Files
qinglong/packages/ql3-cluster-admin/test/workerCredentialAdministration.test.cjs
T

143 lines
4.6 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
WorkerCredentialMutationConflictError,
} = require('@qinglong/runtime-core/worker-credential');
const {
workerCredentialSecretDigest,
} = require('@qinglong/runtime-core/worker-credential-token');
const {
createWorkerCredentialAdministrationService,
} = require('../dist/worker-credential/workerCredentialAdministration');
const NOW = 1_000;
const PEPPER = Buffer.alloc(32, 1).toString('base64url');
const PRINCIPAL = {
subject: { type: 'user', id: 'usr_admin' },
authenticationId: 'session:admin:1',
authenticatedAtMs: 900,
expiresAtMs: 2_000,
assurance: 'multi_factor',
};
function request(overrides = {}) {
return {
mutationId: '123e4567-e89b-42d3-a456-426614174501',
requestId: 'request-worker-issue-1',
expectedCurrentVersion: 0,
credentialId: 'worker_primary',
workerId: 'edge-router-1',
principal: PRINCIPAL,
notBeforeAtMs: NOW,
expiresAtMs: 2_000,
...overrides,
};
}
function repository() {
let mutation = null;
const commands = [];
return {
commands,
port: {
async resolveMutation() { return mutation; },
async append(command) {
commands.push(command);
mutation = {
credential: command.credential,
mutation: command.mutation,
audit: command.audit,
};
return {
status: 'created',
credential: command.credential,
mutation: command.mutation,
};
},
},
setMutation(value) { mutation = value; },
};
}
test('issues a one-time ql3w token and stores only its Worker-domain digest', async () => {
const store = repository();
const generated = Buffer.alloc(32, 7);
const secret = generated.toString('base64url');
const service = createWorkerCredentialAdministrationService(
store.port,
PEPPER,
{ now: () => NOW, randomBytes: () => generated },
);
const result = await service.issue(request());
assert.equal(result.status, 'created');
assert.equal(result.token, `ql3w_worker_primary_${secret}`);
assert.equal(
store.commands[0].credential.secretDigest,
workerCredentialSecretDigest(PEPPER, 'worker_primary', secret),
);
assert.equal(store.commands[0].credential.workerId, 'edge-router-1');
assert.equal(store.commands[0].audit.operationId, 'worker_credential.issue');
assert.equal(generated.every((byte) => byte === 0), true);
});
test('accepts an approved activation time before delayed execution', async () => {
const store = repository();
const service = createWorkerCredentialAdministrationService(
store.port,
PEPPER,
{ now: () => 1_500, randomBytes: () => Buffer.alloc(32, 7) },
);
const result = await service.issue(request());
assert.equal(result.status, 'created');
assert.equal(result.credential.createdAtMs, 1_500);
assert.equal(result.credential.notBeforeAtMs, NOW);
assert.equal(result.credential.expiresAtMs, 2_000);
});
test('semantic replay returns no secret and conflicting replay fails closed', async () => {
const store = repository();
let randomCalls = 0;
const service = createWorkerCredentialAdministrationService(
store.port,
PEPPER,
{
now: () => NOW,
randomBytes() { randomCalls += 1; return Buffer.alloc(32, randomCalls); },
},
);
assert.ok((await service.issue(request())).token);
const replay = await service.issue(request());
assert.equal(replay.status, 'existing');
assert.equal(replay.token, null);
assert.equal(randomCalls, 1);
await assert.rejects(
service.issue(request({ workerId: 'other-worker' })),
WorkerCredentialMutationConflictError,
);
});
test('rejects widened requests, weak principals and operation/version confusion', async () => {
const store = repository();
const service = createWorkerCredentialAdministrationService(
store.port,
PEPPER,
{ now: () => NOW, randomBytes: () => Buffer.alloc(32, 1) },
);
await assert.rejects(service.issue(request({ debug: true })), /shape is invalid/);
await assert.rejects(
service.issue(request({ principal: { ...PRINCIPAL, assurance: 'single_factor' } })),
/strong principal/,
);
await assert.rejects(
service.issue(request({ expectedCurrentVersion: 1 })),
/operation fence is invalid/,
);
const { notBeforeAtMs, expiresAtMs, ...revoke } = request({
mutationId: '123e4567-e89b-42d3-a456-426614174502',
});
assert.equal(notBeforeAtMs, NOW);
assert.equal(expiresAtMs, 2_000);
await assert.rejects(service.revoke(revoke), /operation fence is invalid/);
assert.equal(store.commands.length, 0);
});