Files
qinglong/packages/ql3-local-api/test/admission.test.cjs
T

754 lines
21 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createLocalApiAdmission,
} = require('../dist/admission/localApiAdmission.js');
const PRINCIPAL = Object.freeze({
subject: Object.freeze({ type: 'user', id: 'usr_local' }),
authenticationId: 'credential:local',
authenticatedAtMs: 9_000,
expiresAtMs: 11_000,
assurance: 'single_factor',
});
test('uses task.read for bounded Trigger list and read projections', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: {
operationId: 'trigger.list',
projectId: 'prj_default',
limit: 16,
},
}),
),
{ statusCode: 200, body: { triggers: [], truncated: false } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:task.read:prj_default',
'audit:allowed:trigger.list',
'confirm',
'triggers:prj_default:16',
]);
events.length = 0;
assert.deepEqual(
await execute(
admission,
request({
operation: {
operationId: 'trigger.get',
projectId: 'prj_default',
triggerId: 'cron:task-a',
},
}),
),
{
statusCode: 200,
body: { trigger: { triggerId: 'cron:task-a' } },
},
);
assert.deepEqual(events, [
'authenticate',
'authorize:task.read:prj_default',
'audit:allowed:trigger.get',
'confirm',
'trigger:prj_default:cron:task-a',
]);
});
test('defers Trigger put Policy, presence and mutation to the route', async () => {
const { admission, events } = fixture();
const prepared = await admission.prepare(
request({
localPresence: 'ql3p_bound',
operation: {
operationId: 'trigger.put',
projectId: 'prj_default',
triggerId: 'cron:task-a',
},
}),
);
assert.equal(prepared.bodyMode, 'json');
assert.equal(prepared.maximumBodyBytes, 20 * 1024);
assert.deepEqual(await prepared.handle({ enabled: true }), {
statusCode: 201,
body: { status: 'created' },
});
assert.deepEqual(events, [
'authenticate',
'trigger-put:prj_default:cron:task-a',
]);
});
test('uses secret.manage for bounded Secret metadata and never widens the route input', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: {
operationId: 'secret.list',
projectId: 'prj_default',
limit: 16,
after: { name: 'alpha' },
},
}),
),
{ statusCode: 200, body: { secrets: [], truncated: false } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:secret.manage:prj_default',
'audit:allowed:secret.list',
'confirm',
'secrets:prj_default:16',
]);
});
test('defers Secret put Policy, presence and plaintext body to the fenced route', async () => {
const { admission, events } = fixture();
const prepared = await admission.prepare(
request({
localPresence: 'ql3p_bound',
operation: {
operationId: 'secret.put',
projectId: 'prj_default',
},
}),
);
assert.equal(prepared.bodyMode, 'json');
assert.equal(prepared.maximumBodyBytes, 20 * 1024);
assert.deepEqual(await prepared.handle({ plaintext: 'ephemeral' }), {
statusCode: 201,
body: { status: 'inserted' },
});
assert.deepEqual(events, ['authenticate', 'secret-put:prj_default']);
});
function request(overrides = {}) {
return Object.freeze({
requestId: 'local:019f70c0-0000-7000-8000-000000000001',
operation: Object.freeze({
operationId: 'run.get',
projectId: 'prj_default',
runId: 'run_123',
}),
authorization: 'Bearer opaque',
localPresence: null,
taskAuthoringLease: null,
signal: new AbortController().signal,
...overrides,
});
}
function fixture(overrides = {}) {
const events = [];
const options = {
authenticator: {
async authenticate() {
events.push('authenticate');
return Object.freeze({
principal: PRINCIPAL,
credentialFence: Object.freeze({
credentialId: 'credential-local',
credentialVersion: 1,
pepperKeyId: 'owner-v1',
materialDigest: 'a'.repeat(64),
subjectType: 'user',
subjectId: 'usr_local',
secretDigest: 'b'.repeat(64),
notBeforeAtMs: 1,
expiresAtMs: 20_000,
}),
async confirm() {
events.push('confirm');
},
});
},
},
policy: {
async authorize(principal, projectId, permission) {
assert.equal(principal, PRINCIPAL);
events.push(`authorize:${permission}:${projectId}`);
return {
effect: 'allow',
reasons: ['role_grant'],
fence: { projectVersion: 2, bindingVersion: 3 },
};
},
},
audit: {
async record(record) {
events.push(`audit:${record.outcome}:${record.operationId}`);
},
},
runReadRoute: {
async handle(value) {
events.push(`route:${value.projectId}:${value.runId}`);
return { statusCode: 200, body: { run: { id: value.runId } } };
},
},
runListRoute: {
async handle(value) {
events.push(`list:${value.projectId}:${value.input.limit ?? 32}`);
return { statusCode: 200, body: { runs: [], hasMore: false } };
},
},
runEventListRoute: {
async handle(value) {
events.push(
`events:${value.projectId}:${value.runId}:${
value.input.afterSequence ?? 0
}`,
);
return {
statusCode: 200,
body: { events: [], hasMore: false, nextAfterSequence: 0 },
};
},
},
runStepListRoute: {
async handle(value) {
events.push(
`steps:${value.projectId}:${value.runId}:${
value.input.after?.stepKey ?? 'start'
}`,
);
return {
statusCode: 200,
body: { steps: [], hasMore: false, next: null },
};
},
},
runCancellationRoute: {
async handle(value) {
events.push(`cancel:${value.projectId}:${value.runId}`);
return { statusCode: 202, body: { status: 'accepted' } };
},
},
runAttemptLogReadRoute: {
async handle(value) {
events.push(
`log:${value.projectId}:${value.runId}:${value.attemptId}:${value.offset}:${value.length}`,
);
return { statusCode: 200, body: { status: 'available' } };
},
},
taskListRoute: {
async handle(value) {
events.push(`tasks:${value.projectId}:${value.input.limit ?? 32}`);
return { statusCode: 200, body: { tasks: [], hasMore: false } };
},
},
taskReadRoute: {
async handle(value) {
events.push(`task:${value.projectId}:${value.taskId}`);
return { statusCode: 200, body: { task: { taskId: value.taskId } } };
},
},
taskStartRoute: {
async handle(value) {
events.push(`task-start:${value.projectId}:${value.taskId}`);
return { statusCode: 202, body: { status: 'accepted' } };
},
},
taskPutRoute: {
async handle(value) {
events.push(`task-put:${value.projectId}:${value.taskId}`);
return { statusCode: 201, body: { status: 'created' } };
},
},
taskAuthoringRoute: {
async handle(value) {
events.push(`task-authoring:${value.projectId}:${value.taskId}`);
return { statusCode: 200, body: { task: { taskId: value.taskId } } };
},
},
triggerListRoute: {
async handle(value) {
events.push(`triggers:${value.projectId}:${value.limit}`);
return { statusCode: 200, body: { triggers: [], truncated: false } };
},
},
triggerReadRoute: {
async handle(value) {
events.push(`trigger:${value.projectId}:${value.triggerId}`);
return {
statusCode: 200,
body: { trigger: { triggerId: value.triggerId } },
};
},
},
triggerPutRoute: {
async handle(value) {
events.push(`trigger-put:${value.projectId}:${value.triggerId}`);
return { statusCode: 201, body: { status: 'created' } };
},
},
secretListRoute: {
async handle(value) {
events.push(`secrets:${value.projectId}:${value.limit}`);
return { statusCode: 200, body: { secrets: [], truncated: false } };
},
},
secretPutRoute: {
async handle(value) {
events.push(`secret-put:${value.projectId}`);
return { statusCode: 201, body: { status: 'inserted' } };
},
},
panelCronListRoute: {
async handle(value) {
events.push(
`panel-crons:${value.projectId}:${value.page}:${value.size}:${value.maximumRows}`,
);
return {
statusCode: 200,
body: { code: 200, data: { data: [], total: 0 } },
};
},
},
panelBootstrapRoute: {
async handle(value) {
events.push(
`panel-bootstrap:${value.operationId}:${value.principal.subject.id}`,
);
return {
statusCode: 200,
body: { code: 200, data: { username: value.principal.subject.id } },
};
},
},
now: () => 10_000,
randomUuid: () => '019f70c0-0000-4000-8000-000000000002',
...overrides,
};
return { admission: createLocalApiAdmission(options), events };
}
async function execute(admission, value, body = null) {
const prepared = await admission.prepare(value);
return typeof prepared.handle === 'function'
? prepared.handle(body)
: prepared;
}
test('authenticates, authorizes, durably audits and re-confirms before reading', async () => {
const { admission, events } = fixture();
assert.deepEqual(await execute(admission, request()), {
statusCode: 200,
body: { run: { id: 'run_123' } },
});
assert.deepEqual(events, [
'authenticate',
'authorize:run.read:prj_default',
'audit:allowed:run.get',
'confirm',
'route:prj_default:run_123',
]);
});
test('uses artifact.read and masks denied or approval-fenced log existence', async () => {
const operation = Object.freeze({
operationId: 'run.log.read',
projectId: 'prj_default',
runId: 'run_123',
attemptId: 'attempt_123',
offset: 4,
length: 16,
});
const allowed = fixture();
assert.deepEqual(await execute(allowed.admission, request({ operation })), {
statusCode: 200,
body: { status: 'available' },
});
assert.deepEqual(allowed.events, [
'authenticate',
'authorize:artifact.read:prj_default',
'audit:allowed:run.log.read',
'confirm',
'log:prj_default:run_123:attempt_123:4:16',
]);
for (const effect of ['deny', 'require_approval']) {
let routed = false;
const denied = fixture({
policy: {
async authorize() {
return { effect, reasons: ['masked'], fence: null };
},
},
runAttemptLogReadRoute: {
async handle() {
routed = true;
throw new Error('must not route');
},
},
});
assert.deepEqual(await execute(denied.admission, request({ operation })), {
statusCode: 404,
body: { code: 'artifact_not_found' },
});
assert.equal(routed, false);
}
});
test('uses the same admission chain with a route-owned run.list audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'run.list',
projectId: 'prj_default',
input: Object.freeze({ limit: 8 }),
}),
}),
),
{ statusCode: 200, body: { runs: [], hasMore: false } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:run.read:prj_default',
'audit:allowed:run.list',
'confirm',
'list:prj_default:8',
]);
});
test('uses the same admission chain with a route-owned run.events.list audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'run.events.list',
projectId: 'prj_default',
runId: 'run_123',
input: Object.freeze({ afterSequence: 7, limit: 8 }),
}),
}),
),
{
statusCode: 200,
body: { events: [], hasMore: false, nextAfterSequence: 0 },
},
);
assert.deepEqual(events, [
'authenticate',
'authorize:run.read:prj_default',
'audit:allowed:run.events.list',
'confirm',
'events:prj_default:run_123:7',
]);
});
test('uses the same admission chain with a route-owned run.steps.list audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'run.steps.list',
projectId: 'prj_default',
runId: 'run_123',
input: Object.freeze({
after: Object.freeze({
stepKey: 'build',
stepRunId: 'step_1',
}),
limit: 8,
}),
}),
}),
),
{ statusCode: 200, body: { steps: [], hasMore: false, next: null } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:run.read:prj_default',
'audit:allowed:run.steps.list',
'confirm',
'steps:prj_default:run_123:build',
]);
});
test('uses task.read with a route-owned task.list audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'task.list',
projectId: 'prj_default',
input: Object.freeze({ limit: 8 }),
}),
}),
),
{ statusCode: 200, body: { tasks: [], hasMore: false } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:task.read:prj_default',
'audit:allowed:task.list',
'confirm',
'tasks:prj_default:8',
]);
});
test('uses task.read with a route-owned task.get audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'task.get',
projectId: 'prj_default',
taskId: 'task-a',
}),
}),
),
{ statusCode: 200, body: { task: { taskId: 'task-a' } } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:task.read:prj_default',
'audit:allowed:task.get',
'confirm',
'task:prj_default:task-a',
]);
});
test('uses task.read and the exact panel Cron audit identity for the compatibility projection', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'panel.cron.list',
projectId: 'default',
page: 1,
size: 20,
maximumRows: 64,
}),
}),
),
{
statusCode: 200,
body: { code: 200, data: { data: [], total: 0 } },
},
);
assert.deepEqual(events, [
'authenticate',
'authorize:task.read:default',
'audit:allowed:panel.cron.list',
'confirm',
'panel-crons:default:1:20:64',
]);
});
test('uses the same authenticated task.read chain for the panel bootstrap identity', async () => {
for (const operationId of ['panel.user.get', 'panel.system.config.get']) {
const { admission, events } = fixture();
const result = await execute(
admission,
request({
operation: Object.freeze({ operationId, projectId: 'default' }),
}),
);
assert.equal(result.statusCode, 200);
assert.equal(result.body.data.username, 'usr_local');
assert.deepEqual(events, [
'authenticate',
'authorize:task.read:default',
`audit:allowed:${operationId}`,
'confirm',
`panel-bootstrap:${operationId}:usr_local`,
]);
}
});
test('authorizes and audits run.stop before exposing the cancellation body handler', async () => {
const { admission, events } = fixture();
const prepared = await admission.prepare(
request({
operation: Object.freeze({
operationId: 'run.cancel',
projectId: 'prj_default',
runId: 'run_123',
}),
}),
);
assert.equal(typeof prepared.handle, 'function');
assert.equal(prepared.bodyMode, 'json');
assert.equal(prepared.maximumBodyBytes, 512);
assert.deepEqual(events, [
'authenticate',
'authorize:run.stop:prj_default',
'audit:allowed:run.cancel',
'confirm',
]);
assert.deepEqual(await prepared.handle({ schema: 'x' }), {
statusCode: 202,
body: { status: 'accepted' },
});
assert.equal(events.at(-1), 'cancel:prj_default:run_123');
});
test('authorizes and audits run.start before exposing the Task body handler', async () => {
const { admission, events } = fixture();
const prepared = await admission.prepare(
request({
operation: Object.freeze({
operationId: 'task.start',
projectId: 'prj_default',
taskId: 'task-a',
}),
}),
);
assert.equal(prepared.bodyMode, 'json');
assert.equal(prepared.maximumBodyBytes, 512);
assert.deepEqual(events, [
'authenticate',
'authorize:run.start:prj_default',
'audit:allowed:task.start',
'confirm',
]);
assert.equal((await prepared.handle({ schema: 'x' })).statusCode, 202);
assert.equal(events.at(-1), 'task-start:prj_default:task-a');
});
test('defers Task put Policy, audit and strong confirmation to the request-bound route', async () => {
const { admission, events } = fixture();
const prepared = await admission.prepare(
request({
operation: Object.freeze({
operationId: 'task.put',
projectId: 'prj_default',
taskId: 'task-a',
}),
localPresence: 'ql3p_proof',
}),
);
assert.equal(prepared.bodyMode, 'json');
assert.equal(prepared.maximumBodyBytes, 72 * 1024);
assert.deepEqual(events, ['authenticate']);
assert.deepEqual(await prepared.handle({ name: 'Task' }), {
statusCode: 201,
body: { status: 'created' },
});
assert.deepEqual(events, ['authenticate', 'task-put:prj_default:task-a']);
});
test('defers strong Task authoring read and local presence to the route', async () => {
const { admission, events } = fixture();
const prepared = await admission.prepare(
request({
operation: Object.freeze({
operationId: 'task.authoring',
projectId: 'prj_default',
taskId: 'task-a',
}),
localPresence: 'ql3p_proof',
}),
);
assert.equal(prepared.bodyMode, 'none');
assert.equal(prepared.maximumBodyBytes, 0);
assert.deepEqual(events, ['authenticate']);
assert.deepEqual(await prepared.handle(null), {
statusCode: 200,
body: { task: { taskId: 'task-a' } },
});
assert.deepEqual(events, [
'authenticate',
'task-authoring:prj_default:task-a',
]);
});
test('audits authentication rejection before returning a challenge', async () => {
const events = [];
const { admission } = fixture({
authenticator: {
async authenticate() {
events.push('authenticate');
return null;
},
},
audit: {
async record(record) {
events.push(`audit:${record.outcome}`);
},
},
});
assert.deepEqual(await execute(admission, request()), {
statusCode: 401,
body: { code: 'authentication_required' },
});
assert.deepEqual(events, ['authenticate', 'audit:authentication_rejected']);
});
test('does not confirm or route denied, unaudited or changed credentials', async () => {
const denied = fixture({
policy: {
async authorize() {
return { effect: 'deny', reasons: ['no_binding'], fence: null };
},
},
});
assert.deepEqual(await execute(denied.admission, request()), {
statusCode: 403,
body: { code: 'forbidden' },
});
assert.equal(denied.events.includes('confirm'), false);
assert.equal(
denied.events.some((event) => event.startsWith('route:')),
false,
);
const unaudited = fixture({
audit: {
async record() {
throw new Error('audit unavailable');
},
},
});
assert.deepEqual(await execute(unaudited.admission, request()), {
statusCode: 503,
body: { code: 'security_audit_unavailable' },
});
assert.equal(unaudited.events.includes('confirm'), false);
const changed = fixture({
authenticator: {
async authenticate() {
return {
principal: PRINCIPAL,
async confirm() {
throw new Error('credential rotated');
},
};
},
},
});
assert.deepEqual(await execute(changed.admission, request()), {
statusCode: 503,
body: { code: 'authentication_unavailable' },
});
assert.equal(
changed.events.some((event) => event.startsWith('route:')),
false,
);
});