mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:38:14 +08:00
339 lines
12 KiB
JavaScript
339 lines
12 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { randomUUID } = require('node:crypto');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { test } = require('node:test');
|
|
const {
|
|
WorkerCredentialDeliveryConflictError,
|
|
WorkerCredentialDeliveryUnavailableError,
|
|
workerCredentialDeliveryTokenDigest,
|
|
} = require('@qinglong/runtime-core/worker-credential-delivery');
|
|
const {
|
|
formatWorkerCredentialToken,
|
|
} = require('@qinglong/runtime-core/worker-credential-token');
|
|
const {
|
|
MAX_WORKER_CREDENTIAL_FILE_STAGES,
|
|
WorkerCredentialFileDeliveryAdapter,
|
|
} = require('@qinglong/cluster-admin/worker-credential-file-delivery');
|
|
const {
|
|
createRecoverableWorkerCredentialIssuer,
|
|
} = require('../dist/worker-credential/workerCredentialDelivery');
|
|
|
|
const DELIVERY_ID = '123e4567-e89b-42d3-a456-426614174901';
|
|
const CREDENTIAL_ID = 'worker_generation_2';
|
|
const PREVIOUS_CREDENTIAL_ID = 'worker_generation_1';
|
|
const PEPPER = Buffer.alloc(32, 7).toString('base64url');
|
|
|
|
function fixture(t) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-worker-delivery-'));
|
|
const stages = path.join(root, 'stages');
|
|
const target = path.join(root, 'target');
|
|
fs.mkdirSync(stages, { mode: 0o700 });
|
|
fs.mkdirSync(target, { mode: 0o700 });
|
|
const targetTokenFile = path.join(target, 'credential.token');
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const adapter = new WorkerCredentialFileDeliveryAdapter({
|
|
stageDirectory: stages,
|
|
targetTokenFile,
|
|
});
|
|
return { root, stages, target, targetTokenFile, adapter };
|
|
}
|
|
|
|
function token(credentialId, fill) {
|
|
return Buffer.from(formatWorkerCredentialToken(
|
|
credentialId,
|
|
Buffer.alloc(32, fill).toString('base64url'),
|
|
));
|
|
}
|
|
|
|
function intent(adapter, value, overrides = {}) {
|
|
return {
|
|
deliveryId: DELIVERY_ID,
|
|
workerId: 'edge-router-1',
|
|
credentialId: CREDENTIAL_ID,
|
|
credentialVersion: 1,
|
|
previousCredentialId: PREVIOUS_CREDENTIAL_ID,
|
|
secretDigest: 'a'.repeat(64),
|
|
tokenDigest: workerCredentialDeliveryTokenDigest(value),
|
|
deploymentTargetDigest: adapter.deploymentTargetDigest,
|
|
deploymentGeneration: 'secret-generation-2',
|
|
stagedAtMs: 1_000,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function committed(candidate, overrides = {}) {
|
|
return {
|
|
...candidate,
|
|
version: 1,
|
|
state: 'credential_committed',
|
|
credentialCommittedAtMs: 1_000,
|
|
publishedAtMs: null,
|
|
publicationDigest: null,
|
|
observedAtMs: null,
|
|
observedSessionId: null,
|
|
observedSessionVersion: null,
|
|
previousRevokedAtMs: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function deliveryAuthority() {
|
|
let resolved = null;
|
|
return {
|
|
port: {
|
|
async resolveMutation() { return resolved; },
|
|
async append() { throw new Error('raw append is forbidden'); },
|
|
async resolveDelivery() { return resolved?.delivery ?? null; },
|
|
async resolveDelivered() { return resolved; },
|
|
async commitDelivered(command) {
|
|
resolved = {
|
|
credential: command.credential.credential,
|
|
mutation: command.credential.mutation,
|
|
audit: command.credential.audit,
|
|
delivery: command.delivery,
|
|
};
|
|
return {
|
|
status: 'created',
|
|
credential: resolved.credential,
|
|
mutation: resolved.mutation,
|
|
};
|
|
},
|
|
async markPublished(command) {
|
|
resolved = {
|
|
...resolved,
|
|
delivery: {
|
|
...resolved.delivery,
|
|
version: 2,
|
|
state: 'published',
|
|
publishedAtMs: command.publishedAtMs,
|
|
publicationDigest: command.publicationDigest,
|
|
},
|
|
};
|
|
return resolved.delivery;
|
|
},
|
|
async listRecoveryPage() { throw new Error('not used'); },
|
|
async revokePreviousDelivered() { throw new Error('not used'); },
|
|
async authorizeStageDiscard() { throw new Error('not used'); },
|
|
async markStageDiscarded() { throw new Error('not used'); },
|
|
async listStageDiscardRecoveryPage() { throw new Error('not used'); },
|
|
},
|
|
current: () => resolved,
|
|
};
|
|
}
|
|
|
|
test('stages one private no-replace secret and inspects only its intent', async (t) => {
|
|
const { adapter, stages } = fixture(t);
|
|
const material = token(CREDENTIAL_ID, 2);
|
|
const candidate = intent(adapter, material);
|
|
await adapter.stage(candidate, material);
|
|
material.fill(0);
|
|
assert.deepEqual(await adapter.inspect(DELIVERY_ID), candidate);
|
|
const stagePath = path.join(stages, `${DELIVERY_ID}.stage`);
|
|
assert.equal(fs.statSync(stagePath).mode & 0o777, 0o600);
|
|
assert.deepEqual(fs.readdirSync(stages), [`${DELIVERY_ID}.stage`]);
|
|
|
|
const replay = token(CREDENTIAL_ID, 2);
|
|
await adapter.stage(candidate, replay);
|
|
await assert.rejects(
|
|
adapter.stage({ ...candidate, deploymentGeneration: 'other-generation' }, replay),
|
|
WorkerCredentialDeliveryConflictError,
|
|
);
|
|
replay.fill(0);
|
|
assert.equal((await adapter.inspect(DELIVERY_ID)).deploymentGeneration,
|
|
'secret-generation-2');
|
|
});
|
|
|
|
test('atomically replaces only the expected previous credential and replays publication', async (t) => {
|
|
const { adapter, targetTokenFile, stages, target } = fixture(t);
|
|
const previous = token(PREVIOUS_CREDENTIAL_ID, 1);
|
|
fs.writeFileSync(targetTokenFile, previous, { mode: 0o600 });
|
|
previous.fill(0);
|
|
const material = token(CREDENTIAL_ID, 2);
|
|
const candidate = intent(adapter, material);
|
|
await adapter.stage(candidate, material);
|
|
const publication = await adapter.publish(committed(candidate));
|
|
assert.match(publication.publicationDigest, /^[0-9a-f]{64}$/);
|
|
assert.deepEqual(fs.readFileSync(targetTokenFile), material);
|
|
assert.equal(fs.statSync(targetTokenFile).mode & 0o777, 0o600);
|
|
assert.deepEqual(fs.readdirSync(target), ['credential.token']);
|
|
assert.deepEqual(fs.readdirSync(stages), [`${DELIVERY_ID}.stage`]);
|
|
|
|
const restarted = new WorkerCredentialFileDeliveryAdapter({
|
|
stageDirectory: stages,
|
|
targetTokenFile,
|
|
});
|
|
assert.deepEqual(
|
|
await restarted.publish(committed(candidate)),
|
|
publication,
|
|
);
|
|
material.fill(0);
|
|
});
|
|
|
|
test('completes stage-before-commit issuance through the concrete file adapter', async (t) => {
|
|
const { adapter, targetTokenFile } = fixture(t);
|
|
const authority = deliveryAuthority();
|
|
const generated = Buffer.alloc(32, 9);
|
|
const service = createRecoverableWorkerCredentialIssuer(
|
|
authority.port,
|
|
adapter,
|
|
PEPPER,
|
|
{ now: () => 1_000, randomBytes: () => generated },
|
|
);
|
|
const result = await service.issue({
|
|
mutationId: DELIVERY_ID,
|
|
requestId: 'request-file-delivery-1',
|
|
expectedCurrentVersion: 0,
|
|
credentialId: CREDENTIAL_ID,
|
|
workerId: 'edge-router-1',
|
|
principal: {
|
|
subject: { type: 'user', id: 'usr_admin' },
|
|
authenticationId: 'session:admin:1',
|
|
authenticatedAtMs: 900,
|
|
expiresAtMs: 2_000,
|
|
assurance: 'multi_factor',
|
|
},
|
|
notBeforeAtMs: 1_000,
|
|
expiresAtMs: 2_000,
|
|
previousCredentialId: null,
|
|
deploymentTargetDigest: adapter.deploymentTargetDigest,
|
|
deploymentGeneration: 'secret-generation-2',
|
|
});
|
|
assert.equal(result.status, 'published');
|
|
assert.equal(result.delivery.state, 'published');
|
|
assert.match(fs.readFileSync(targetTokenFile, 'ascii'),
|
|
/^ql3w_worker_generation_2_[A-Za-z0-9_-]{43}$/);
|
|
assert.equal(JSON.stringify(authority.current()).includes('ql3w_'), false);
|
|
assert.equal(generated.every((byte) => byte === 0), true);
|
|
});
|
|
|
|
test('fails closed instead of overwriting an unexpected target generation', async (t) => {
|
|
const { adapter, targetTokenFile } = fixture(t);
|
|
const unexpected = token('worker_generation_other', 3);
|
|
fs.writeFileSync(targetTokenFile, unexpected, { mode: 0o600 });
|
|
const material = token(CREDENTIAL_ID, 2);
|
|
const candidate = intent(adapter, material);
|
|
await adapter.stage(candidate, material);
|
|
await assert.rejects(
|
|
adapter.publish(committed(candidate)),
|
|
WorkerCredentialDeliveryConflictError,
|
|
);
|
|
assert.deepEqual(fs.readFileSync(targetTokenFile), unexpected);
|
|
unexpected.fill(0);
|
|
material.fill(0);
|
|
});
|
|
|
|
test('discards only an unpublished exact orphan stage', async (t) => {
|
|
const { adapter, targetTokenFile } = fixture(t);
|
|
const material = token(CREDENTIAL_ID, 2);
|
|
const candidate = intent(adapter, material);
|
|
await adapter.stage(candidate, material);
|
|
await adapter.discard(candidate);
|
|
assert.equal(await adapter.inspect(DELIVERY_ID), null);
|
|
await adapter.discard(candidate);
|
|
|
|
await adapter.stage(candidate, material);
|
|
fs.writeFileSync(targetTokenFile, material, { mode: 0o600 });
|
|
await assert.rejects(
|
|
adapter.discard(candidate),
|
|
WorkerCredentialDeliveryConflictError,
|
|
);
|
|
assert.deepEqual(await adapter.inspect(DELIVERY_ID), candidate);
|
|
material.fill(0);
|
|
});
|
|
|
|
test('uses a durable target lock and leaves the old token untouched on contention', async (t) => {
|
|
const { adapter, target, targetTokenFile } = fixture(t);
|
|
const previous = token(PREVIOUS_CREDENTIAL_ID, 1);
|
|
fs.writeFileSync(targetTokenFile, previous, { mode: 0o600 });
|
|
fs.writeFileSync(
|
|
path.join(target, '.ql3-worker-credential-delivery.lock'),
|
|
`${JSON.stringify({ deliveryId: randomUUID() })}\n`,
|
|
{ mode: 0o600 },
|
|
);
|
|
const material = token(CREDENTIAL_ID, 2);
|
|
const candidate = intent(adapter, material);
|
|
await adapter.stage(candidate, material);
|
|
await assert.rejects(
|
|
adapter.publish(committed(candidate)),
|
|
WorkerCredentialDeliveryUnavailableError,
|
|
);
|
|
assert.deepEqual(fs.readFileSync(targetTokenFile), previous);
|
|
previous.fill(0);
|
|
material.fill(0);
|
|
});
|
|
|
|
test('lists only bounded ordered low-sensitive stage intents', async (t) => {
|
|
const { adapter, stages } = fixture(t);
|
|
const firstToken = token(CREDENTIAL_ID, 2);
|
|
const secondToken = token(CREDENTIAL_ID, 3);
|
|
const first = intent(adapter, firstToken);
|
|
const second = intent(adapter, secondToken, {
|
|
deliveryId: '123e4567-e89b-42d3-a456-426614174902',
|
|
tokenDigest: workerCredentialDeliveryTokenDigest(secondToken),
|
|
deploymentGeneration: 'secret-generation-3',
|
|
});
|
|
await adapter.stage(second, secondToken);
|
|
await adapter.stage(first, firstToken);
|
|
assert.deepEqual(await adapter.listStaged({ limit: 1 }), {
|
|
stages: [first],
|
|
truncated: true,
|
|
nextCursor: first.deliveryId,
|
|
});
|
|
assert.deepEqual(await adapter.listStaged({
|
|
afterDeliveryId: first.deliveryId,
|
|
limit: 1,
|
|
}), {
|
|
stages: [second],
|
|
truncated: false,
|
|
});
|
|
fs.writeFileSync(
|
|
path.join(stages, `.${DELIVERY_ID}.${randomUUID()}.tmp`),
|
|
'uncertain',
|
|
{ mode: 0o600 },
|
|
);
|
|
await assert.rejects(
|
|
adapter.listStaged(),
|
|
WorkerCredentialDeliveryUnavailableError,
|
|
);
|
|
firstToken.fill(0);
|
|
secondToken.fill(0);
|
|
});
|
|
|
|
test('enforces stage capacity, dedicated roots and live POSIX permissions', async (t) => {
|
|
const { adapter, stages, target, targetTokenFile } = fixture(t);
|
|
const material = token(CREDENTIAL_ID, 2);
|
|
const candidate = intent(adapter, material);
|
|
for (let index = 0; index < MAX_WORKER_CREDENTIAL_FILE_STAGES; index += 1) {
|
|
fs.writeFileSync(
|
|
path.join(stages, `${randomUUID()}.stage`),
|
|
'bounded',
|
|
{ mode: 0o600 },
|
|
);
|
|
}
|
|
await assert.rejects(
|
|
adapter.stage(candidate, material),
|
|
WorkerCredentialDeliveryUnavailableError,
|
|
);
|
|
fs.rmSync(stages, { recursive: true });
|
|
fs.mkdirSync(stages, { mode: 0o700 });
|
|
const changed = new WorkerCredentialFileDeliveryAdapter({
|
|
stageDirectory: stages,
|
|
targetTokenFile,
|
|
});
|
|
const changedCandidate = intent(changed, material);
|
|
await changed.stage(changedCandidate, material);
|
|
fs.chmodSync(target, 0o755);
|
|
await assert.rejects(
|
|
changed.publish(committed(changedCandidate)),
|
|
WorkerCredentialDeliveryUnavailableError,
|
|
);
|
|
fs.chmodSync(target, 0o700);
|
|
assert.throws(() => new WorkerCredentialFileDeliveryAdapter({
|
|
stageDirectory: target,
|
|
targetTokenFile,
|
|
}));
|
|
material.fill(0);
|
|
});
|