mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
405 lines
12 KiB
JavaScript
405 lines
12 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
consumeApprovalRequest,
|
|
createApprovalRequest,
|
|
decideApprovalRequest,
|
|
} = require('@qinglong/runtime-core/approved-action');
|
|
const {
|
|
claimApprovedActionExecution,
|
|
completeApprovedActionExecution,
|
|
createApprovedActionExecution,
|
|
startApprovedActionExecution,
|
|
} = require('@qinglong/runtime-core/approved-action-execution');
|
|
const {
|
|
ClusterApprovalManagementTransportAuthenticationError,
|
|
ClusterApprovalManagementTransportRequestError,
|
|
createClusterApprovalManagementTransport,
|
|
} = require('@qinglong/cluster-admin/approval-management-transport');
|
|
|
|
const ACTION = Object.freeze({
|
|
permission: 'run.start',
|
|
actionType: 'tool.invoke',
|
|
actionRef: 'tool:task-1',
|
|
actionDigest: 'a'.repeat(64),
|
|
previewDigest: 'b'.repeat(64),
|
|
});
|
|
const PRINCIPAL = Object.freeze({
|
|
subject: Object.freeze({ type: 'user', id: 'owner-1' }),
|
|
authenticationId: 'oidc:session-1',
|
|
authenticatedAtMs: 1_000,
|
|
expiresAtMs: 20_000,
|
|
assurance: 'hardware',
|
|
});
|
|
const BASE_REQUEST = Object.freeze({
|
|
projectId: 'default',
|
|
approvalRequestId: 'approval-1',
|
|
requestId: 'approval-command-1',
|
|
auditEventId: '40000000-0000-4000-8000-000000000001',
|
|
failureAuditEventId: '40000000-0000-4000-8000-000000000002',
|
|
});
|
|
const RECOVERY_BASE_REQUEST = Object.freeze({
|
|
projectId: 'default',
|
|
dispatchId: 'dispatch-1',
|
|
requestId: 'recovery-command-1',
|
|
auditEventId: '40000000-0000-4000-8000-000000000003',
|
|
failureAuditEventId: '40000000-0000-4000-8000-000000000004',
|
|
});
|
|
|
|
function pending() {
|
|
return createApprovalRequest({
|
|
id: 'approval-1',
|
|
projectId: 'default',
|
|
action: ACTION,
|
|
risk: 'high',
|
|
decisionMode: 'human_confirmation',
|
|
requestedBy: { type: 'agent', id: 'agent-1' },
|
|
requestedAtMs: 900,
|
|
expiresAtMs: 10_000,
|
|
requestFence: { projectVersion: 1, bindingVersion: 2 },
|
|
});
|
|
}
|
|
|
|
function inspectCommand() {
|
|
return {
|
|
schemaVersion: 1,
|
|
operation: 'approval.inspect',
|
|
request: BASE_REQUEST,
|
|
};
|
|
}
|
|
|
|
function decideCommand() {
|
|
return {
|
|
schemaVersion: 1,
|
|
operation: 'approval.decide',
|
|
request: {
|
|
...BASE_REQUEST,
|
|
expectedVersion: 1,
|
|
expectedAction: ACTION,
|
|
decisionId: 'decision-1',
|
|
decision: 'approved',
|
|
reasonCode: 'reviewed',
|
|
},
|
|
};
|
|
}
|
|
|
|
function executingRecoverySnapshot() {
|
|
const action = {
|
|
...ACTION,
|
|
permission: 'secret.manage',
|
|
actionType: 'plugin_package.secret_binding.bind',
|
|
actionRef: 'secret-binding:1',
|
|
};
|
|
const recoveryPending = createApprovalRequest({
|
|
id: 'approval-1',
|
|
projectId: 'default',
|
|
action,
|
|
risk: 'high',
|
|
decisionMode: 'human_confirmation',
|
|
requestedBy: { type: 'agent', id: 'agent-1' },
|
|
requestedAtMs: 900,
|
|
expiresAtMs: 10_000,
|
|
requestFence: { projectVersion: 1, bindingVersion: 2 },
|
|
});
|
|
const approved = decideApprovalRequest(recoveryPending, {
|
|
expectedVersion: 1,
|
|
decisionId: 'decision-recovery-1',
|
|
decision: 'approved',
|
|
reasonCode: 'reviewed',
|
|
principal: PRINCIPAL,
|
|
decidedAtMs: 1_100,
|
|
authorizationFence: { projectVersion: 1, bindingVersion: 2 },
|
|
});
|
|
const dispatch = consumeApprovalRequest(approved, {
|
|
expectedVersion: 2,
|
|
consumptionId: 'consumption-1',
|
|
dispatchId: 'dispatch-1',
|
|
action,
|
|
requestedBy: approved.requestedBy,
|
|
consumedBy: { type: 'system', id: 'package-executor' },
|
|
consumedAtMs: 1_200,
|
|
authorizationFence: { projectVersion: 1, bindingVersion: 2 },
|
|
}).dispatch;
|
|
const leased = claimApprovedActionExecution(createApprovedActionExecution(dispatch), {
|
|
owner: 'executor-1',
|
|
leaseToken: 'lease-1',
|
|
nowMs: 1_300,
|
|
leaseDurationMs: 500,
|
|
});
|
|
const execution = startApprovedActionExecution(
|
|
{ dispatch, execution: leased },
|
|
{
|
|
dispatchId: dispatch.id,
|
|
approvalRequestId: dispatch.approvalRequestId,
|
|
actionDigest: dispatch.action.actionDigest,
|
|
owner: leased.leaseOwner,
|
|
leaseToken: leased.leaseToken,
|
|
expectedVersion: leased.version,
|
|
startedAtMs: 1_400,
|
|
},
|
|
);
|
|
return { execution: { dispatch, execution }, resolution: null };
|
|
}
|
|
|
|
test('inspects and decides through fresh strong authentication without leaking principal facts', async () => {
|
|
const calls = [];
|
|
const failures = [];
|
|
const transport = createClusterApprovalManagementTransport({
|
|
service: {
|
|
async inspect(request, confirmAuthorization) {
|
|
calls.push(['inspect', request]);
|
|
await confirmAuthorization();
|
|
return {
|
|
request: pending(),
|
|
preview: {
|
|
title: 'Run task',
|
|
summary: 'Runs one reviewed task.',
|
|
fields: [{ kind: 'identifier', label: 'Task', value: 'task-1' }],
|
|
warnings: ['external_effect'],
|
|
},
|
|
};
|
|
},
|
|
async decide(request, confirmAuthorization) {
|
|
calls.push(['decide', request]);
|
|
await confirmAuthorization();
|
|
return {
|
|
status: 'decided',
|
|
request: decideApprovalRequest(pending(), {
|
|
expectedVersion: 1,
|
|
decisionId: request.decisionId,
|
|
decision: request.decision,
|
|
reasonCode: request.reasonCode,
|
|
principal: request.principal,
|
|
decidedAtMs: 2_000,
|
|
authorizationFence: { projectVersion: 1, bindingVersion: 2 },
|
|
}),
|
|
};
|
|
},
|
|
async inspectRecovery() {
|
|
throw new Error('not used');
|
|
},
|
|
async resolveRecovery() {
|
|
throw new Error('not used');
|
|
},
|
|
async recordFailure(record) {
|
|
failures.push(record);
|
|
},
|
|
},
|
|
now: () => 2_000,
|
|
});
|
|
let authenticationCalls = 0;
|
|
const authentication = {
|
|
async authenticate() {
|
|
authenticationCalls += 1;
|
|
return PRINCIPAL;
|
|
},
|
|
};
|
|
|
|
const inspected = await transport.execute(inspectCommand(), authentication);
|
|
assert.equal(authenticationCalls, 2);
|
|
assert.equal(inspected.status, 'found');
|
|
assert.equal(inspected.approval.preview.title, 'Run task');
|
|
assert.equal(inspected.approval.expectedAction.actionDigest, 'a'.repeat(64));
|
|
|
|
const decided = await transport.execute(decideCommand(), authentication);
|
|
assert.equal(authenticationCalls, 4);
|
|
assert.equal(decided.status, 'decided');
|
|
assert.equal(decided.approval.state, 'approved');
|
|
assert.equal(decided.approval.version, 2);
|
|
assert.deepEqual(calls.map(([operation]) => operation), ['inspect', 'decide']);
|
|
assert.equal(failures.length, 0);
|
|
assert.doesNotMatch(
|
|
JSON.stringify([inspected, decided]),
|
|
/authenticationId|authenticatedAtMs|assurance/,
|
|
);
|
|
});
|
|
|
|
test('records unauthenticated and reauthentication failures with schema-valid identities', async () => {
|
|
const failures = [];
|
|
const service = {
|
|
async inspect(_request, confirmAuthorization) {
|
|
await confirmAuthorization();
|
|
return null;
|
|
},
|
|
async decide() {
|
|
throw new Error('not used');
|
|
},
|
|
async inspectRecovery() {
|
|
throw new Error('not used');
|
|
},
|
|
async resolveRecovery() {
|
|
throw new Error('not used');
|
|
},
|
|
async recordFailure(record) {
|
|
failures.push(record);
|
|
},
|
|
};
|
|
const transport = createClusterApprovalManagementTransport({
|
|
service,
|
|
now: () => 2_000,
|
|
});
|
|
|
|
await assert.rejects(
|
|
transport.execute(inspectCommand(), {
|
|
async authenticate() {
|
|
return { ...PRINCIPAL, assurance: 'single_factor' };
|
|
},
|
|
}),
|
|
ClusterApprovalManagementTransportAuthenticationError,
|
|
);
|
|
assert.equal(failures[0].outcome, 'authentication_rejected');
|
|
assert.equal(failures[0].subject, null);
|
|
assert.equal(failures[0].authenticationId, null);
|
|
|
|
let calls = 0;
|
|
await assert.rejects(
|
|
transport.execute(inspectCommand(), {
|
|
async authenticate() {
|
|
calls += 1;
|
|
return calls === 1
|
|
? PRINCIPAL
|
|
: { ...PRINCIPAL, authenticationId: 'oidc:session-2' };
|
|
},
|
|
}),
|
|
ClusterApprovalManagementTransportAuthenticationError,
|
|
);
|
|
assert.equal(failures[1].outcome, 'denied');
|
|
assert.deepEqual(failures[1].subject, PRINCIPAL.subject);
|
|
assert.equal(failures[1].authenticationId, PRINCIPAL.authenticationId);
|
|
});
|
|
|
|
test('rejects widened or ambiguously audited commands before authentication', async () => {
|
|
let authenticationCalls = 0;
|
|
const transport = createClusterApprovalManagementTransport({
|
|
service: {
|
|
async inspect() {},
|
|
async decide() {},
|
|
async inspectRecovery() {},
|
|
async resolveRecovery() {},
|
|
async recordFailure() {},
|
|
},
|
|
});
|
|
await assert.rejects(
|
|
transport.execute(
|
|
{
|
|
...inspectCommand(),
|
|
extra: true,
|
|
},
|
|
{
|
|
async authenticate() {
|
|
authenticationCalls += 1;
|
|
return PRINCIPAL;
|
|
},
|
|
},
|
|
),
|
|
ClusterApprovalManagementTransportRequestError,
|
|
);
|
|
await assert.rejects(
|
|
transport.execute(
|
|
{
|
|
...inspectCommand(),
|
|
request: {
|
|
...BASE_REQUEST,
|
|
failureAuditEventId: BASE_REQUEST.auditEventId,
|
|
},
|
|
},
|
|
{
|
|
async authenticate() {
|
|
authenticationCalls += 1;
|
|
return PRINCIPAL;
|
|
},
|
|
},
|
|
),
|
|
ClusterApprovalManagementTransportRequestError,
|
|
);
|
|
assert.equal(authenticationCalls, 0);
|
|
});
|
|
|
|
test('inspects and resolves recovery without exposing execution lease or authentication facts', async () => {
|
|
const source = executingRecoverySnapshot();
|
|
const nextExecution = completeApprovedActionExecution(source.execution.execution, {
|
|
owner: source.execution.execution.leaseOwner,
|
|
leaseToken: source.execution.execution.leaseToken,
|
|
expectedVersion: source.execution.execution.version,
|
|
resultMutationId: 'manual-recovery-1',
|
|
outcome: 'indeterminate',
|
|
resultCode: 'manual_recovery_abandoned_unknown',
|
|
completedAtMs: 2_000,
|
|
});
|
|
const resolution = {
|
|
mutationId: 'manual-recovery-1',
|
|
decision: 'abandon_unknown',
|
|
evidenceDigest: 'e'.repeat(64),
|
|
reasonCode: 'orphan_absence_verified',
|
|
resolvedBy: { type: 'user', id: 'owner-1' },
|
|
resolvedAtMs: 2_000,
|
|
resolutionDigest: 'f'.repeat(64),
|
|
};
|
|
const transport = createClusterApprovalManagementTransport({
|
|
service: {
|
|
async inspect() {},
|
|
async decide() {},
|
|
async inspectRecovery(_request, confirmAuthorization) {
|
|
await confirmAuthorization();
|
|
return source;
|
|
},
|
|
async resolveRecovery(_request, confirmAuthorization) {
|
|
await confirmAuthorization();
|
|
return {
|
|
status: 'resolved',
|
|
snapshot: {
|
|
execution: { dispatch: source.execution.dispatch, execution: nextExecution },
|
|
resolution: {
|
|
schema: 'qinglong/approved-action-manual-recovery@v1',
|
|
dispatchId: 'dispatch-1',
|
|
dispatchDigest: source.execution.execution.dispatchDigest,
|
|
projectId: 'default',
|
|
actionType: source.execution.dispatch.action.actionType,
|
|
actionDigest: source.execution.dispatch.action.actionDigest,
|
|
executionVersion: source.execution.execution.version,
|
|
executionDigest: source.execution.execution.executionDigest,
|
|
authenticationId: PRINCIPAL.authenticationId,
|
|
assurance: PRINCIPAL.assurance,
|
|
authenticatedAtMs: PRINCIPAL.authenticatedAtMs,
|
|
authorizationFence: { projectVersion: 1, bindingVersion: 2 },
|
|
auditEventId: RECOVERY_BASE_REQUEST.auditEventId,
|
|
...resolution,
|
|
},
|
|
},
|
|
};
|
|
},
|
|
async recordFailure() {},
|
|
},
|
|
now: () => 2_000,
|
|
});
|
|
const authentication = { async authenticate() { return PRINCIPAL; } };
|
|
const inspected = await transport.execute(
|
|
{ schemaVersion: 1, operation: 'approval.recover.inspect', request: RECOVERY_BASE_REQUEST },
|
|
authentication,
|
|
);
|
|
const resolved = await transport.execute(
|
|
{
|
|
schemaVersion: 1,
|
|
operation: 'approval.recover.resolve',
|
|
request: {
|
|
...RECOVERY_BASE_REQUEST,
|
|
expectedExecutionVersion: source.execution.execution.version,
|
|
expectedExecutionDigest: source.execution.execution.executionDigest,
|
|
mutationId: resolution.mutationId,
|
|
decision: resolution.decision,
|
|
evidenceDigest: resolution.evidenceDigest,
|
|
reasonCode: resolution.reasonCode,
|
|
},
|
|
},
|
|
authentication,
|
|
);
|
|
assert.equal(inspected.recovery.execution.status, 'recovery_required');
|
|
assert.equal(resolved.recovery.execution.status, 'blocked');
|
|
assert.equal(resolved.recovery.resolution.decision, 'abandon_unknown');
|
|
assert.doesNotMatch(
|
|
JSON.stringify([inspected, resolved]),
|
|
/leaseOwner|leaseToken|authenticationId|authenticatedAtMs|assurance/,
|
|
);
|
|
});
|