Files
qinglong/packages/ql3-cluster-admin/test/approvalManagementClient.test.cjs

342 lines
9.1 KiB
JavaScript

const assert = require('node:assert/strict');
const {
chmodSync,
mkdtempSync,
readFileSync,
realpathSync,
rmSync,
writeFileSync,
} = require('node:fs');
const { tmpdir } = require('node:os');
const { join, resolve } = require('node:path');
const { afterEach, test } = require('node:test');
const {
ClusterPluginPackageManagementClientRequestError,
} = require('@qinglong/cluster-admin/plugin-package-management-client');
const {
executeClusterApprovalManagementClient,
validateClusterApprovalManagementClientResult,
} = require('@qinglong/cluster-admin/approval-management-client');
const FIXTURES = resolve(
__dirname,
'../../ql3-cluster-control/test/fixtures/mtls',
);
const temporaryDirectories = [];
const ACTION = Object.freeze({
permission: 'run.start',
actionType: 'tool.invoke',
actionRef: 'tool:task-1',
actionDigest: 'a'.repeat(64),
previewDigest: 'b'.repeat(64),
});
const BASE_REQUEST = Object.freeze({
projectId: 'default',
approvalRequestId: 'approval-1',
requestId: 'approval-command-1',
auditEventId: '60000000-0000-4000-8000-000000000001',
failureAuditEventId: '60000000-0000-4000-8000-000000000002',
});
const RECOVERY_BASE_REQUEST = Object.freeze({
projectId: 'default',
dispatchId: 'dispatch-1',
requestId: 'recovery-command-1',
auditEventId: '60000000-0000-4000-8000-000000000003',
failureAuditEventId: '60000000-0000-4000-8000-000000000004',
});
function privateWrite(filePath, value) {
writeFileSync(filePath, value, { mode: 0o600 });
chmodSync(filePath, 0o600);
}
function clientFiles() {
const directory = realpathSync(
mkdtempSync(join(tmpdir(), 'ql3-approval-client-')),
);
temporaryDirectories.push(directory);
const paths = {
configFile: join(directory, 'client.json'),
commandFile: join(directory, 'command.json'),
assertionFile: join(directory, 'assertion.jwt'),
};
const caFile = join(directory, 'ca.crt');
const clientCertificateFile = join(directory, 'client.crt');
const clientPrivateKeyFile = join(directory, 'client.key');
privateWrite(caFile, readFileSync(join(FIXTURES, 'ca-cert.pem')));
privateWrite(
clientCertificateFile,
readFileSync(join(FIXTURES, 'client-cert.pem')),
);
privateWrite(
clientPrivateKeyFile,
readFileSync(join(FIXTURES, 'client-key.pem')),
);
privateWrite(
paths.configFile,
`${JSON.stringify({
schemaVersion: 1,
endpoint:
'https://approval.example.test:8447/api/v3/approvals/management',
servername: 'approval.example.test',
caFile,
clientCertificateFile,
clientPrivateKeyFile,
requestTimeoutMs: 1_000,
})}\n`,
);
privateWrite(
paths.commandFile,
`${JSON.stringify({
schemaVersion: 1,
operation: 'approval.inspect',
request: BASE_REQUEST,
})}\n`,
);
privateWrite(
paths.assertionFile,
'eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJ1In0.c2lnbmF0dXJl',
);
return paths;
}
afterEach(() => {
for (const directory of temporaryDirectories.splice(0)) {
rmSync(directory, { recursive: true, force: true });
}
});
test('validates exact inspect results and binds them to the requested Approval', () => {
const command = {
schemaVersion: 1,
operation: 'approval.inspect',
request: BASE_REQUEST,
};
const found = {
schemaVersion: 1,
operation: 'approval.inspect',
status: 'found',
approval: {
projectId: 'default',
approvalRequestId: 'approval-1',
version: 1,
state: 'pending',
risk: 'high',
decisionMode: 'human_confirmation',
expectedAction: ACTION,
requestedBy: { type: 'agent', id: 'agent-1' },
requestedAtMs: 1_000,
expiresAtMs: 10_000,
preview: {
title: 'Run task',
summary: 'Runs one reviewed task.',
fields: [{ kind: 'identifier', label: 'Task', value: 'task-1' }],
warnings: ['external_effect'],
},
},
};
assert.deepEqual(
validateClusterApprovalManagementClientResult(found, command),
found,
);
assert.deepEqual(
validateClusterApprovalManagementClientResult(
{ ...found, status: 'absent', approval: null },
command,
),
{ ...found, status: 'absent', approval: null },
);
assert.throws(
() =>
validateClusterApprovalManagementClientResult(
{
...found,
approval: { ...found.approval, projectId: 'other' },
},
command,
),
ClusterPluginPackageManagementClientRequestError,
);
});
test('validates the durable decision tuple and rejects server-side drift', () => {
const command = {
schemaVersion: 1,
operation: 'approval.decide',
request: {
...BASE_REQUEST,
expectedVersion: 1,
expectedAction: ACTION,
decisionId: 'decision-1',
decision: 'approved',
reasonCode: 'reviewed',
},
};
const result = {
schemaVersion: 1,
operation: 'approval.decide',
status: 'decided',
approval: {
projectId: 'default',
approvalRequestId: 'approval-1',
version: 2,
state: 'approved',
expectedAction: ACTION,
decisionId: 'decision-1',
decision: 'approved',
reasonCode: 'reviewed',
decidedBy: { type: 'user', id: 'owner-1' },
decidedAtMs: 2_000,
},
};
assert.deepEqual(
validateClusterApprovalManagementClientResult(result, command),
result,
);
for (const approval of [
{ ...result.approval, version: 1 },
{ ...result.approval, state: 'rejected' },
{ ...result.approval, decisionId: 'decision-2' },
]) {
assert.throws(
() =>
validateClusterApprovalManagementClientResult(
{ ...result, approval },
command,
),
ClusterPluginPackageManagementClientRequestError,
);
}
});
test('validates recovery inspection and binds terminal resolution to the command fence', () => {
const recoveryAction = {
...ACTION,
permission: 'secret.manage',
actionType: 'plugin_package.secret_binding.bind',
actionRef: 'secret-binding:1',
};
const inspectCommand = {
schemaVersion: 1,
operation: 'approval.recover.inspect',
request: RECOVERY_BASE_REQUEST,
};
const recovery = {
projectId: 'default',
dispatchId: 'dispatch-1',
approvalRequestId: 'approval-1',
expectedAction: recoveryAction,
execution: {
status: 'recovery_required',
version: 3,
executionDigest: 'c'.repeat(64),
attemptCount: 1,
maxAttempts: 3,
startedAtMs: 1_400,
leaseExpiresAtMs: 1_800,
resultMutationId: null,
resultCode: null,
resultDigest: null,
completedAtMs: null,
createdAtMs: 1_200,
updatedAtMs: 1_400,
},
resolution: null,
};
assert.deepEqual(
validateClusterApprovalManagementClientResult(
{
schemaVersion: 1,
operation: inspectCommand.operation,
status: 'found',
recovery,
},
inspectCommand,
).recovery,
recovery,
);
const resolveCommand = {
schemaVersion: 1,
operation: 'approval.recover.resolve',
request: {
...RECOVERY_BASE_REQUEST,
expectedExecutionVersion: 3,
expectedExecutionDigest: 'c'.repeat(64),
mutationId: 'manual-recovery-1',
decision: 'abandon_unknown',
evidenceDigest: 'e'.repeat(64),
reasonCode: 'orphan_absence_verified',
},
};
const resolved = {
...recovery,
execution: {
...recovery.execution,
status: 'blocked',
version: 4,
executionDigest: 'd'.repeat(64),
leaseExpiresAtMs: null,
resultMutationId: 'manual-recovery-1',
resultCode: 'manual_recovery_abandoned_unknown',
completedAtMs: 2_000,
updatedAtMs: 2_000,
},
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 result = {
schemaVersion: 1,
operation: resolveCommand.operation,
status: 'resolved',
recovery: resolved,
};
assert.deepEqual(
validateClusterApprovalManagementClientResult(result, resolveCommand),
result,
);
for (const changed of [
{ ...resolved, execution: { ...resolved.execution, version: 3 } },
{
...resolved,
resolution: { ...resolved.resolution, evidenceDigest: '0'.repeat(64) },
},
]) {
assert.throws(
() =>
validateClusterApprovalManagementClientResult(
{ ...result, recovery: changed },
resolveCommand,
),
ClusterPluginPackageManagementClientRequestError,
);
}
});
test('accepts only the exact Approval route before opening one mTLS connection', async () => {
let connects = 0;
await assert.rejects(
executeClusterApprovalManagementClient(clientFiles(), {
async connect(target) {
connects += 1;
assert.deepEqual(target, {
hostname: 'approval.example.test',
port: 8447,
});
throw new Error('expected-connect-stop');
},
}),
{ code: 'QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_REQUEST_FAILED' },
);
assert.equal(connects, 1);
});