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

290 lines
8.9 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
consumeApprovalRequest,
createApprovalRequest,
decideApprovalRequest,
} = require('@qinglong/runtime-core/approved-action');
const {
claimApprovedActionExecution,
createApprovedActionExecution,
startApprovedActionExecution,
} = require('@qinglong/runtime-core/approved-action-execution');
const {
createPluginPackageResourceGeneration,
} = require('@qinglong/runtime-core/plugin-package-resource-generation');
const {
createPluginPackageSecretBinding,
} = require('@qinglong/runtime-core/plugin-package-secret-binding');
const {
createPluginPackageSecretBindingTransitionApprovalPlan,
pluginPackageSecretBindingTransitionApprovedAction,
} = require('@qinglong/runtime-core/plugin-package-secret-binding-transition-approval-plan');
const {
createPluginPackageSecretBindingTransitionPlan,
} = require('@qinglong/runtime-core/plugin-package-secret-binding-transition-plan');
const { createSecretRef } = require('@qinglong/runtime-core/secret-reference');
const {
ClusterPluginPackageSecretBindingTransitionApprovedActionHandler,
} = require('@qinglong/cluster-admin/plugin-package-secret-binding-transition-approved-action');
const REQUESTER = Object.freeze({ type: 'user', id: 'cluster-owner' });
const REVIEWER = Object.freeze({ type: 'user', id: 'security-reviewer' });
const CONSUMER = Object.freeze({
type: 'system',
id: 'cluster_package_executor',
});
const FENCE = Object.freeze({ projectVersion: 3, bindingVersion: 4 });
function manifest(version) {
return {
apiVersion: 'qinglong.io/v1alpha1',
kind: 'Package',
metadata: {
name: 'example-monitor',
displayName: 'Example Monitor',
version,
description: 'Secret transition handler fixture',
license: 'Apache-2.0',
},
spec: {
compatibility: {
qinglong: '>=3.0.0-0 <4.0.0',
architectures: ['arm64'],
deploymentProfiles: ['cluster-control'],
},
runtimes: [],
resources: {
memory: { recommended: '32Mi' },
disk: { install: '4Mi', working: '8Mi' },
},
permissions: {
network: { allowedHosts: [] },
secrets: [{ name: 'TOKEN', required: true }],
tools: ['secret.use'],
},
contents: { tasks: [], workflows: [], prompts: [], tools: [] },
},
};
}
function approvalPlan() {
const previousManifest = manifest('1.0.0');
const previousGeneration = createPluginPackageResourceGeneration({
installationId: 'install-secret-transition-v1',
projectId: 'project-1',
packageName: 'example-monitor',
lockDigest: 'a'.repeat(64),
generation: 1,
previousActiveLockDigest: null,
contentDigest: 'b'.repeat(64),
contents: previousManifest.spec.contents,
});
const previousBinding = createPluginPackageSecretBinding({
generation: previousGeneration,
manifest: previousManifest,
assignments: [{
name: 'TOKEN',
secretRef: createSecretRef({
projectId: 'project-1',
name: 'runtime-token',
version: 1,
}),
}],
authority: {
kind: 'approved-action-execution',
evidenceDigest: 'c'.repeat(64),
},
boundAtMs: 80,
});
const nextManifest = manifest('2.0.0');
const transitionPlan = createPluginPackageSecretBindingTransitionPlan({
previousTarget: previousBinding.target,
previousBinding,
previousAttemptGeneration: 1,
nextGeneration: createPluginPackageResourceGeneration({
installationId: 'install-secret-transition-v2',
projectId: 'project-1',
packageName: 'example-monitor',
lockDigest: 'd'.repeat(64),
generation: 2,
previousActiveLockDigest: 'a'.repeat(64),
contentDigest: 'e'.repeat(64),
contents: nextManifest.spec.contents,
}),
nextManifest,
assignments: [{
name: 'TOKEN',
secretRef: createSecretRef({
projectId: 'project-1',
name: 'runtime-token',
version: 2,
}),
}],
plannedAtMs: 100,
});
return createPluginPackageSecretBindingTransitionApprovalPlan({
actionRef: 'secret-transition:example-monitor-v2',
transitionPlan,
requestedBy: REQUESTER,
plannedAtMs: 100,
expiresAtMs: 1_000,
});
}
function dispatch(plan) {
const action = pluginPackageSecretBindingTransitionApprovedAction(plan);
const pending = createApprovalRequest({
id: 'approval-secret-transition-1',
projectId: 'project-1',
action,
risk: 'high',
decisionMode: 'separation_of_duty',
requestedBy: REQUESTER,
requestedAtMs: 110,
expiresAtMs: 900,
requestFence: FENCE,
});
const approved = decideApprovalRequest(pending, {
expectedVersion: 1,
decisionId: 'decision-secret-transition-1',
decision: 'approved',
reasonCode: 'reviewed',
principal: {
subject: REVIEWER,
authenticationId: 'auth-reviewer',
authenticatedAtMs: 100,
expiresAtMs: 800,
assurance: 'multi_factor',
},
decidedAtMs: 120,
authorizationFence: FENCE,
});
return consumeApprovalRequest(approved, {
expectedVersion: 2,
consumptionId: 'consume-secret-transition-1',
dispatchId: 'dispatch-secret-transition-1',
action,
requestedBy: REQUESTER,
consumedBy: CONSUMER,
consumedAtMs: 130,
authorizationFence: FENCE,
}).dispatch;
}
function execution(approvedDispatch, startedAtMs = 140) {
const claimed = claimApprovedActionExecution(
createApprovedActionExecution(approvedDispatch, 5),
{
owner: 'secret-transition-executor',
leaseToken: 'lease-secret-transition-1',
nowMs: 131,
leaseDurationMs: 2_000,
},
);
return startApprovedActionExecution(
{ dispatch: approvedDispatch, execution: claimed },
{
dispatchId: approvedDispatch.id,
approvalRequestId: approvedDispatch.approvalRequestId,
actionDigest: approvedDispatch.action.actionDigest,
owner: claimed.leaseOwner,
leaseToken: claimed.leaseToken,
expectedVersion: claimed.version,
startedAtMs,
},
);
}
test('commits exactly one approved transition and verifies projected references', async () => {
const plan = approvalPlan();
const approvedDispatch = dispatch(plan);
const started = execution(approvedDispatch);
const applied = [];
const inspected = [];
const subject = new ClusterPluginPackageSecretBindingTransitionApprovedActionHandler(
{ async findByActionRef() { return plan; } },
{
async apply(input) {
applied.push(input);
return {
status: 'created',
receipt: { receiptDigest: 'f'.repeat(64) },
};
},
},
{ async assertExists(refs) { inspected.push(...refs); } },
);
assert.deepEqual(await subject.inspect(approvedDispatch), {
status: 'ready',
actionDigest: plan.approvalPlanDigest,
});
const result = await subject.execute({
dispatch: approvedDispatch,
execution: started,
idempotencyKey: approvedDispatch.id,
fence: {
owner: started.leaseOwner,
leaseToken: started.leaseToken,
version: started.version,
},
});
assert.deepEqual(result, {
outcome: 'succeeded',
resultCode: 'package_secret_transition_committed',
resultDigest: 'f'.repeat(64),
});
assert.equal(applied.length, 1);
assert.deepEqual(applied[0], {
transitionPlan: plan.transitionPlan,
evidenceDigest: plan.approvalPlanDigest,
committedAtMs: 140,
});
assert.deepEqual(inspected, [
plan.transitionPlan.nextBindingPlan.entries[0].secretRef,
plan.transitionPlan.nextBindingPlan.entries[0].secretRef,
]);
});
test('blocks plan drift and execution that starts after plan expiry', async () => {
const plan = approvalPlan();
const approvedDispatch = dispatch(plan);
const applied = [];
const subject = new ClusterPluginPackageSecretBindingTransitionApprovedActionHandler(
{ async findByActionRef() { return plan; } },
{ async apply(input) { applied.push(input); throw new Error('must not apply'); } },
{ async assertExists() {} },
);
const late = execution(approvedDispatch, 1_001);
assert.deepEqual(
await subject.execute({
dispatch: approvedDispatch,
execution: late,
idempotencyKey: approvedDispatch.id,
fence: {
owner: late.leaseOwner,
leaseToken: late.leaseToken,
version: late.version,
},
}),
{
outcome: 'failed',
resultCode: 'package_secret_transition_plan_rejected',
},
);
assert.equal(applied.length, 0);
const drifted = { ...plan, approvalPlanDigest: '9'.repeat(64) };
const driftedSubject = new ClusterPluginPackageSecretBindingTransitionApprovedActionHandler(
{ async findByActionRef() { return drifted; } },
{ async apply() { throw new Error('must not apply'); } },
{ async assertExists() {} },
);
assert.deepEqual(await driftedSubject.inspect(approvedDispatch), {
status: 'blocked',
resultCode: 'package_secret_transition_plan_rejected',
});
});