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

444 lines
13 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 {
createClusterAdministrationCommandRunner,
normalizeClusterAdministrationCommand,
publishClusterAdministrationCredentialDelivery,
} = require('@qinglong/cluster-admin/administration-command');
const PRINCIPAL = Object.freeze({
subject: { type: 'user', id: 'security-owner' },
authenticationId: 'assertion:security-command-1',
authenticatedAtMs: 900,
expiresAtMs: 2_000,
assurance: 'multi_factor',
});
const SUBJECT = Object.freeze({ type: 'api_app', id: 'automation-client' });
const PATHS = Object.freeze({
commandFile: '/private/command.json',
assertionFile: '/private/assertion.jwt',
keysetFile: '/private/keyset.json',
pepperFile: '/private/pepper',
});
function identityCommand(overrides = {}) {
return {
schemaVersion: 1,
operation: 'identity.register',
request: {
mutationId: '123e4567-e89b-42d3-a456-426614174301',
requestId: 'security-identity-register-1',
expectedCurrentVersion: 0,
subject: SUBJECT,
},
...overrides,
};
}
function credentialCommand(overrides = {}) {
return {
schemaVersion: 1,
operation: 'credential.issue',
request: {
mutationId: '123e4567-e89b-42d3-a456-426614174302',
requestId: 'security-credential-issue-1',
expectedCurrentVersion: 0,
credentialId: 'automation-primary',
subject: SUBJECT,
notBeforeAtMs: 1_000,
expiresAtMs: 2_000,
},
...overrides,
};
}
function authority(overrides = {}) {
const calls = [];
let closes = 0;
const credential = {
credentialId: 'automation-primary',
subject: SUBJECT,
secretDigest: 'digest',
pepperKeyId: 'legacy-v1',
state: 'active',
version: 1,
createdAtMs: 1_000,
notBeforeAtMs: 1_000,
expiresAtMs: 2_000,
};
const value = {
administration: {
async registerIdentity(request) {
calls.push(['identity.register', request]);
return {
status: 'inserted',
identity: {
subject: request.subject,
status: 'active',
version: 1,
createdAtMs: 1_000,
updatedAtMs: 1_000,
},
mutation: {},
};
},
async enableIdentity() {
throw new Error('unexpected enable');
},
async disableIdentity() {
throw new Error('unexpected disable');
},
async issueCredential(request) {
calls.push(['credential.issue', request]);
return {
status: 'inserted',
credential,
mutation: {},
token: 'ql3c_automation-primary_private-secret',
};
},
async rotateCredential() {
throw new Error('unexpected rotate');
},
async revokeCredential() {
throw new Error('unexpected revoke');
},
},
audit: {
async list(query) {
calls.push(['audit.list', query]);
return { records: [], nextCursor: null };
},
},
pepperReferences: {
async inspect(pepperKeyId, limit) {
calls.push(['pepper.references', { pepperKeyId, limit }]);
return {
pepperKeyId,
observedAtMs: 1_000,
credentialIds: ['automation-primary'],
hasMore: false,
};
},
},
async close() {
closes += 1;
},
...overrides,
};
return { value, calls, closes: () => closes };
}
function runner(command, authorityValue, published = []) {
const buffers = [];
const pepper = 'A'.repeat(43);
const files = new Map([
[PATHS.commandFile, `${JSON.stringify(command)}\n`],
[PATHS.assertionFile, 'signed.assertion.value'],
[PATHS.pepperFile, pepper],
]);
const authentications = [];
const opens = [];
const instance = createClusterAdministrationCommandRunner({
async openAuthority(environment, candidatePepper) {
opens.push({ environment, pepper: candidatePepper });
return authorityValue;
},
async authenticate(keysetFile, assertion) {
authentications.push({ keysetFile, assertion });
return PRINCIPAL;
},
readFile(filePath) {
const value = files.get(filePath);
if (value === undefined) throw new Error(`unexpected file: ${filePath}`);
const buffer = Buffer.from(value);
buffers.push(buffer);
return buffer;
},
publishDelivery(filePath, bytes) {
published.push({ filePath, bytes: Buffer.from(bytes) });
},
});
return { instance, buffers, authentications, opens };
}
test('executes one strongly authenticated identity mutation and closes authority', async () => {
const target = authority();
const execution = runner(identityCommand(), target.value);
const result = await execution.instance.run(PATHS, { deployment: 'test' });
assert.deepEqual(result, {
schemaVersion: 1,
operation: 'identity.register',
status: 'inserted',
subject: SUBJECT,
version: 1,
identityStatus: 'active',
});
assert.deepEqual(execution.authentications, [
{
keysetFile: PATHS.keysetFile,
assertion: 'signed.assertion.value',
},
]);
assert.deepEqual(execution.opens[0].pepper, {
schemaVersion: 1,
activePepperKeyId: 'legacy-v1',
keys: [{ pepperKeyId: 'legacy-v1', pepper: 'A'.repeat(43) }],
});
assert.equal(target.calls[0][1].principal, PRINCIPAL);
assert.equal(target.closes(), 1);
assert.equal(
execution.buffers.every((value) => value.every((byte) => byte === 0)),
true,
);
});
test('publishes a credential token only to the private delivery boundary', async () => {
const target = authority();
const published = [];
const execution = runner(credentialCommand(), target.value, published);
const paths = { ...PATHS, deliveryFile: '/private/delivery.json' };
const result = await execution.instance.run(paths, {});
assert.equal(result.operation, 'credential.issue');
assert.equal(result.status, 'inserted');
assert.equal('token' in result, false);
assert.deepEqual(result.delivery.fileName, 'delivery.json');
assert.match(result.delivery.digest, /^[0-9a-f]{64}$/);
assert.equal(published.length, 1);
const delivery = JSON.parse(published[0].bytes.toString('utf8'));
assert.equal(delivery.token, 'ql3c_automation-primary_private-secret');
assert.equal(delivery.mutationId, credentialCommand().request.mutationId);
assert.equal(target.closes(), 1);
});
test('does not recreate lost token material during exact credential replay', async () => {
const base = authority();
base.value.administration.issueCredential = async () => ({
status: 'existing',
credential: {
credentialId: 'automation-primary',
subject: SUBJECT,
state: 'active',
version: 1,
createdAtMs: 1_000,
notBeforeAtMs: 1_000,
expiresAtMs: 2_000,
},
mutation: {},
token: null,
});
const published = [];
const execution = runner(credentialCommand(), base.value, published);
const result = await execution.instance.run(
{ ...PATHS, deliveryFile: '/private/delivery.json' },
{},
);
assert.equal(result.status, 'existing');
assert.equal('delivery' in result, false);
assert.deepEqual(published, []);
assert.equal(base.closes(), 1);
});
test('revokes a credential without requiring or publishing a delivery file', async () => {
const target = authority();
target.value.administration.revokeCredential = async (request) => ({
status: 'inserted',
credential: {
credentialId: request.credentialId,
subject: request.subject,
state: 'revoked',
version: 2,
createdAtMs: 1_000,
notBeforeAtMs: 1_000,
expiresAtMs: 2_000,
},
mutation: {},
});
const published = [];
const execution = runner(
{
schemaVersion: 1,
operation: 'credential.revoke',
request: {
mutationId: '123e4567-e89b-42d3-a456-426614174303',
requestId: 'security-credential-revoke-1',
expectedCurrentVersion: 1,
credentialId: 'automation-primary',
subject: SUBJECT,
},
},
target.value,
published,
);
const result = await execution.instance.run(PATHS, {});
assert.deepEqual(result, {
schemaVersion: 1,
operation: 'credential.revoke',
status: 'inserted',
subject: SUBJECT,
credentialId: 'automation-primary',
version: 2,
state: 'revoked',
});
assert.deepEqual(published, []);
assert.equal(target.closes(), 1);
});
test('keeps audit query bounded and rejects widened command shapes before admission', async () => {
const query = {
schemaVersion: 1,
operation: 'audit.list',
request: { limit: 25, filter: { outcome: 'allowed' } },
};
const target = authority();
const execution = runner(query, target.value);
const result = await execution.instance.run(PATHS, {});
assert.deepEqual(result, {
schemaVersion: 1,
operation: 'audit.list',
page: { records: [], nextCursor: null },
});
assert.deepEqual(target.calls, [
['audit.list', { limit: 25, filter: { outcome: 'allowed' } }],
]);
assert.throws(
() => normalizeClusterAdministrationCommand({ ...query, debug: true }),
/command shape is invalid/,
);
assert.throws(
() =>
normalizeClusterAdministrationCommand({
...query,
request: { limit: 201, filter: {} },
}),
/audit query is invalid/,
);
});
test('loads a dual-generation keyring and exposes bounded retirement references', async () => {
const target = authority();
const nextPepper = Buffer.alloc(32, 2).toString('base64url');
const command = {
schemaVersion: 1,
operation: 'pepper.references',
request: { pepperKeyId: 'legacy-v1', limit: 32 },
};
const paths = {
commandFile: PATHS.commandFile,
assertionFile: PATHS.assertionFile,
keysetFile: PATHS.keysetFile,
pepperKeyringFile: '/private/pepper-keyring.json',
};
const buffers = [];
const opens = [];
const files = new Map([
[paths.commandFile, JSON.stringify(command)],
[paths.assertionFile, 'signed.assertion.value'],
[
paths.pepperKeyringFile,
JSON.stringify({
schemaVersion: 1,
activePepperKeyId: 'rotation-2026-08',
keys: [
{ pepperKeyId: 'legacy-v1', pepper: 'A'.repeat(43) },
{ pepperKeyId: 'rotation-2026-08', pepper: nextPepper },
],
}),
],
]);
const instance = createClusterAdministrationCommandRunner({
async openAuthority(environment, keyring) {
opens.push({ environment, keyring });
return target.value;
},
async authenticate() {
return PRINCIPAL;
},
readFile(filePath) {
const buffer = Buffer.from(files.get(filePath));
buffers.push(buffer);
return buffer;
},
publishDelivery() {
throw new Error('unexpected delivery');
},
});
assert.deepEqual(await instance.run(paths, {}), {
schemaVersion: 1,
operation: 'pepper.references',
pepperKeyId: 'legacy-v1',
observedAtMs: 1_000,
credentialIds: ['automation-primary'],
hasMore: false,
});
assert.equal(opens[0].keyring.activePepperKeyId, 'rotation-2026-08');
assert.equal(opens[0].keyring.keys.length, 2);
assert.equal(target.calls[0][0], 'pepper.references');
assert.equal(target.closes(), 1);
assert.equal(
buffers.every((buffer) => buffer.every((byte) => byte === 0)),
true,
);
});
test('rejects widened path authority before reading a command file', async () => {
let reads = 0;
const instance = createClusterAdministrationCommandRunner({
async openAuthority() {
throw new Error('must not open authority');
},
async authenticate() {
throw new Error('must not authenticate');
},
readFile() {
reads += 1;
throw new Error('must not read');
},
publishDelivery() {
throw new Error('must not publish');
},
});
await assert.rejects(
instance.run({ ...PATHS, ambientCredential: true }, {}),
/command paths shape is invalid/,
);
assert.equal(reads, 0);
});
test('publishes a 0600 no-replace delivery and leaves an existing target intact', (t) => {
const directory = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-security-delivery-'),
);
fs.chmodSync(directory, 0o700);
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
const filePath = path.join(directory, 'credential.json');
const bytes = Buffer.from('{"token":"secret"}\n');
publishClusterAdministrationCredentialDelivery(filePath, bytes);
assert.equal(fs.statSync(filePath).mode & 0o777, 0o600);
assert.deepEqual(fs.readFileSync(filePath), bytes);
assert.throws(
() =>
publishClusterAdministrationCredentialDelivery(
filePath,
Buffer.from('{"token":"replacement"}\n'),
),
/could not be published/,
);
assert.deepEqual(fs.readFileSync(filePath), bytes);
});