Files
qinglong/packages/ql3-cluster-postgres/test/pluginPackagePublisherTrustTransitionProposalRepository.test.cjs

331 lines
9.8 KiB
JavaScript

const assert = require('node:assert/strict');
const { generateKeyPairSync } = require('node:crypto');
const { test } = require('node:test');
const {
approvalRequestDigest,
createApprovalRequest,
decideApprovalRequest,
} = require('@qinglong/runtime-core/approved-action');
const {
createPluginPackagePublisherTrustSnapshot,
} = require('@qinglong/runtime-core/plugin-package-publisher-trust');
const {
createPluginPackagePublisherTrustTransitionProposal,
PluginPackagePublisherTrustTransitionConflictError,
} = require('@qinglong/runtime-core/plugin-package-publisher-trust-transition-proposal');
const {
PostgresPluginPackagePublisherTrustTransitionProposalRepository,
} = require('../dist/plugin-package/publisher/pluginPackagePublisherTrustTransitionProposalRepository');
const SUBJECT = Object.freeze({ type: 'user', id: 'usr_owner' });
const FENCE = Object.freeze({ projectVersion: 4, bindingVersion: 7 });
function transition() {
const oldPair = generateKeyPairSync('ed25519');
const newPair = generateKeyPairSync('ed25519');
const oldDefinition = {
publisher: 'publisher-a.example',
keyId: 'key-old',
publicKeyPem: oldPair.publicKey.export({
type: 'spki',
format: 'pem',
}),
notBeforeMs: 1,
notAfterMs: 10_000,
};
const newDefinition = {
publisher: 'publisher-a.example',
keyId: 'key-new',
publicKeyPem: newPair.publicKey.export({
type: 'spki',
format: 'pem',
}),
notBeforeMs: 1,
notAfterMs: 20_000,
};
const currentSnapshot =
createPluginPackagePublisherTrustSnapshot([oldDefinition]);
const materialSnapshot =
createPluginPackagePublisherTrustSnapshot([
oldDefinition,
newDefinition,
]);
const created =
createPluginPackagePublisherTrustTransitionProposal({
actionRef: 'publisher-overlap:publisher-a.example:key-new',
authorityProjectId: 'cluster-trust-authority',
trustAuthorityId: 'cluster',
trustGeneration: 1,
mode: 'overlap_add',
trustSnapshot: currentSnapshot,
materialSnapshot,
publisher: 'publisher-a.example',
keyId: 'key-new',
proposedBy: SUBJECT,
proposerAssurance: 'multi_factor',
proposalFence: FENCE,
createdAtMs: 100,
});
return {
...created,
currentSnapshot,
};
}
function audit(proposal, overrides = {}) {
return {
eventId: '33000000-0000-4000-8000-000000000001',
requestId: proposal.actionRef,
operationId: 'plugin_package.publisher_trust_transition.propose',
projectId: proposal.projectId,
subject: proposal.proposedBy,
authenticationId: 'auth-owner',
outcome: 'allowed',
reasons: ['publisher_trust_transition_proposal'],
fence: proposal.proposalFence,
occurredAtMs: proposal.createdAtMs,
...overrides,
};
}
function fixture(value, trustOverrides = {}) {
const snapshots = new Map([
[value.currentSnapshot.snapshotDigest, value.currentSnapshot],
]);
let storedProposal = null;
let storedAudit = null;
let signerLocks = 0;
const query = async (text, values = []) => {
if (text.includes('pg_advisory_xact_lock(hashtextextended')) {
signerLocks += 1;
assert.deepEqual(values, [
JSON.stringify([
value.proposal.actionInput.publisher,
value.proposal.actionInput.keyId,
]),
774635229,
]);
return { rows: [{}], rowCount: 1 };
}
if (
text.includes(
'FROM "ql3"."plugin_package_publisher_trust_transition_proposals"',
)
) {
return {
rows: storedProposal
? [
{
proposalJson: storedProposal,
proposalDigest: storedProposal.proposalDigest,
},
]
: [],
};
}
if (
text.includes(
'FROM "ql3"."plugin_package_publisher_trust_heads" AS head',
)
) {
return {
rows: [
{
generation:
trustOverrides.generation ??
value.proposal.actionInput.trustGeneration,
effectiveTrustDigest:
trustOverrides.effectiveTrustDigest ??
value.proposal.actionInput.previousTrustDigest,
snapshotJson: value.currentSnapshot,
snapshotDigest: value.currentSnapshot.snapshotDigest,
},
],
};
}
if (
text.includes(
'INSERT INTO "ql3"."plugin_package_publisher_trust_snapshots"',
)
) {
const existed = snapshots.has(values[0]);
if (!existed) snapshots.set(values[0], JSON.parse(values[4]));
return { rows: [], rowCount: existed ? 0 : 1 };
}
if (
text.includes(
'FROM "ql3"."plugin_package_publisher_trust_snapshots"',
)
) {
const snapshot = snapshots.get(values[0]);
return {
rows: snapshot
? [
{
snapshotJson: snapshot,
snapshotDigest: snapshot.snapshotDigest,
},
]
: [],
};
}
if (text.includes('lock_approval_policy_fence')) {
return { rows: [{ matches: true }] };
}
if (
text.includes(
'"ql3"."plugin_package_publisher_trust_transition_proposals" (',
)
) {
storedProposal = JSON.parse(values[19]);
return { rows: [], rowCount: 1 };
}
if (text.includes('INSERT INTO "ql3"."security_audit_events"')) {
storedAudit = audit(value.proposal);
return { rows: [], rowCount: 1 };
}
if (text.includes('FROM "ql3"."security_audit_events"')) {
return {
rows: storedAudit
? [
{
eventId: storedAudit.eventId,
requestId: storedAudit.requestId,
operationId: storedAudit.operationId,
projectId: storedAudit.projectId,
subjectType: storedAudit.subject.type,
subjectId: storedAudit.subject.id,
authenticationId: storedAudit.authenticationId,
outcome: storedAudit.outcome,
reasons: storedAudit.reasons,
projectVersion: storedAudit.fence.projectVersion,
bindingVersion: storedAudit.fence.bindingVersion,
occurredAtMs: storedAudit.occurredAtMs,
},
]
: [],
};
}
return { rows: [], rowCount: 0 };
};
const client = { query, release() {} };
const repository =
new PostgresPluginPackagePublisherTrustTransitionProposalRepository({
query,
async connect() {
return client;
},
});
repository.signerLocks = () => signerLocks;
repository.snapshots = () => snapshots;
return repository;
}
test('persists candidate material and exactly replays an overlap proposal', async () => {
const value = transition();
const repository = fixture(value);
const command = {
proposal: value.proposal,
candidateSnapshot: value.candidateSnapshot,
audit: audit(value.proposal),
};
const created = await repository.createProposal(command);
assert.equal(created.status, 'created');
assert.equal(repository.signerLocks(), 1);
assert.deepEqual(
repository.snapshots().get(value.candidateSnapshot.snapshotDigest),
value.candidateSnapshot,
);
const replay = await repository.createProposal(command);
assert.equal(replay.status, 'existing');
assert.equal(repository.signerLocks(), 1);
assert.deepEqual(
await repository.findProposalByActionRef(value.proposal.actionRef),
value.proposal,
);
});
test('rejects stale trust generations and client-shaped candidate drift', async () => {
const value = transition();
await assert.rejects(
fixture(value, { generation: 2 }).createProposal({
proposal: value.proposal,
candidateSnapshot: value.candidateSnapshot,
audit: audit(value.proposal),
}),
PluginPackagePublisherTrustTransitionConflictError,
);
assert.throws(
() =>
fixture(value).createProposal({
proposal: value.proposal,
candidateSnapshot: value.currentSnapshot,
audit: audit(value.proposal),
}),
PluginPackagePublisherTrustTransitionConflictError,
);
});
test('lists only separation-of-duty approvals from the durable JSON record', async () => {
const value = transition();
const pending = createApprovalRequest({
id: 'approval-trust-transition-list',
projectId: value.proposal.projectId,
action: {
permission: value.proposal.permission,
actionType: value.proposal.actionType,
actionRef: value.proposal.actionRef,
actionDigest: value.proposal.actionDigest,
previewDigest: value.proposal.previewDigest,
},
risk: 'critical',
decisionMode: 'separation_of_duty',
requestedBy: SUBJECT,
requestedAtMs: 100,
expiresAtMs: 10_000,
requestFence: FENCE,
});
const approved = decideApprovalRequest(pending, {
expectedVersion: 1,
decisionId: 'decision-trust-transition-list',
decision: 'approved',
reasonCode: 'reviewed',
principal: {
subject: { type: 'user', id: 'usr_reviewer' },
authenticationId: 'auth-reviewer',
authenticatedAtMs: 100,
expiresAtMs: 10_000,
assurance: 'multi_factor',
},
decidedAtMs: 101,
authorizationFence: FENCE,
});
let inspectedSql = '';
const repository =
new PostgresPluginPackagePublisherTrustTransitionProposalRepository({
async query(text) {
inspectedSql = text;
return {
rows: [
{
requestJson: approved,
requestDigest: approvalRequestDigest(approved),
},
],
rowCount: 1,
};
},
async connect() {
throw new Error('listApprovedRequests must not open a transaction');
},
});
assert.deepEqual(await repository.listApprovedRequests(4), [approved]);
assert.match(
inspectedSql,
/request\.request_json ->> 'decisionMode'/,
);
assert.doesNotMatch(inspectedSql, /request\.decision_mode/);
});