Files
qinglong/packages/ql3-local-owner-maintenance/test/acknowledgementGc.test.cjs
T

162 lines
5.5 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
openLocalSqliteBootstrapDatabase,
} = require('@qinglong/local-sqlite/bootstrap');
const { migrateLocalSqlitePath } = require('@qinglong/local-sqlite/migration');
const {
MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_AUDIT_RETENTION_MS,
MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_REPLAY_RETENTION_MS,
} = require('@qinglong/runtime-core/local-owner-delivery-acknowledgement-gc');
const {
LocalOwnerDeliveryAcknowledgementGcConfigurationError,
openLocalOwnerDeliveryAcknowledgementGc,
} = require('../dist/security-maintenance/acknowledgementGc');
const NOW = 1_760_000_000_000;
const ACK_MUTATION_ID = '00000000-0000-4000-8000-000000000e01';
const GC_MUTATION_ID = '00000000-0000-4000-8000-000000000e02';
const DELIVERY_DIGEST = 'd'.repeat(64);
const POLICY = Object.freeze({
version: 1,
replayRetentionMs: MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_REPLAY_RETENTION_MS,
auditRetentionMs: MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_AUDIT_RETENTION_MS,
});
const COMPACTED_AT_MS =
NOW + MIN_LOCAL_OWNER_ACKNOWLEDGEMENT_AUDIT_RETENTION_MS + 1_000;
function request(overrides = {}) {
return {
mutationId: GC_MUTATION_ID,
requestId: 'ack-gc-e02',
acknowledgementMutationId: ACK_MUTATION_ID,
expectedKind: 'credential',
expectedDeliveryDigest: DELIVERY_DIGEST,
...overrides,
};
}
async function fixture(t) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-ack-gc-e2e-'));
const secretDeliveryDirectory = path.join(root, 'secrets');
const databasePath = path.join(root, 'qinglong3.sqlite');
fs.mkdirSync(secretDeliveryDirectory, { mode: 0o700 });
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const databaseOptions = { databasePath, profile: 'edge' };
await migrateLocalSqlitePath(databaseOptions);
const database = await openLocalSqliteBootstrapDatabase(databaseOptions);
await database.ownerPepper.register({
mutationId: '00000000-0000-4000-8000-000000000e91',
pepperKeyId: 'legacy-v1',
materialDigest: 'a'.repeat(64),
backupDigest: 'b'.repeat(64),
registeredAtMs: NOW - 2_000,
});
await database.ownerPepper.activate({
mutationId: '00000000-0000-4000-8000-000000000e92',
pepperKeyId: 'legacy-v1',
expectedGeneration: 0,
activatedAtMs: NOW - 1_000,
});
const subjectId = `usr_${Buffer.alloc(16, 41).toString('base64url')}`;
const credentialId = `own_${Buffer.alloc(16, 42).toString('base64url')}`;
await database.ownerBootstrap.provision({
mutationId: ACK_MUTATION_ID,
requestId: 'provision-e01',
identity: {
subject: { type: 'user', id: subjectId },
status: 'active',
version: 1,
createdAtMs: NOW,
updatedAtMs: NOW,
},
credential: {
credentialId,
version: 1,
pepperKeyId: 'legacy-v1',
state: 'active',
subject: { type: 'user', id: subjectId },
subjectStatus: 'active',
secretDigest: 'c'.repeat(64),
createdAtMs: NOW,
notBeforeAtMs: NOW,
expiresAtMs: NOW + 600_000,
},
issuer: {
subject: { type: 'system', id: 'owner-bootstrap' },
authenticationId: 'local-console-test',
authenticatedAtMs: NOW - 1_000,
expiresAtMs: NOW + 60_000,
assurance: 'local_console',
},
audit: {
eventId: ACK_MUTATION_ID,
requestId: 'provision-e01',
operationId: 'identity.bootstrap_provision',
projectId: null,
subject: { type: 'system', id: 'owner-bootstrap' },
authenticationId: 'local-console-test',
outcome: 'allowed',
reasons: ['local_console_provisioning'],
fence: null,
occurredAtMs: NOW,
},
createdAtMs: NOW,
});
await database.ownerBootstrap.recordDeliveryAcknowledgement({
kind: 'credential',
mutationId: ACK_MUTATION_ID,
requestId: 'provision-e01',
subjectId,
credentialId,
factDigest: 'c'.repeat(64),
deliveryDigest: DELIVERY_DIGEST,
ttlMs: 600_000,
acknowledgedAtMs: NOW + 1,
});
await database.close();
return {
databasePath,
profile: 'edge',
secretDeliveryDirectory,
retentionPolicy: POLICY,
};
}
test('derives trusted bridge evidence and replays one durable compaction', async (t) => {
const options = await fixture(t);
t.mock.method(Date, 'now', () => COMPACTED_AT_MS);
const authority = await openLocalOwnerDeliveryAcknowledgementGc(options);
t.after(() => authority.close());
const inserted = await authority.compact(request());
assert.equal(inserted.status, 'inserted');
assert.equal(inserted.record.compactedAtMs, COMPACTED_AT_MS);
assert.equal(inserted.record.bridgeClearEvidenceDigest.length, 64);
const replay = await authority.compact(request());
assert.equal(replay.status, 'existing');
assert.deepEqual(replay.record, inserted.record);
});
test('rejects a live file bridge and caller-controlled time', async (t) => {
const options = await fixture(t);
t.mock.method(Date, 'now', () => COMPACTED_AT_MS);
fs.writeFileSync(
path.join(
options.secretDeliveryDirectory,
`credential-${ACK_MUTATION_ID}.ready.json`,
),
'{}',
{ mode: 0o600 },
);
const authority = await openLocalOwnerDeliveryAcknowledgementGc(options);
t.after(() => authority.close());
await assert.rejects(authority.compact(request()), /bridge is not clear/);
await assert.rejects(
authority.compact({ ...request(), compactedAtMs: COMPACTED_AT_MS }),
LocalOwnerDeliveryAcknowledgementGcConfigurationError,
);
});