const assert = require('node:assert/strict'); const { chmod, mkdtemp, rename, rm, writeFile } = require('node:fs/promises'); const { tmpdir } = require('node:os'); const { join } = require('node:path'); const { generateKeyPairSync, sign } = require('node:crypto'); const { test } = require('node:test'); const { ClusterPluginPackageIdentityKeysetUnavailableError, createClusterPluginPackageIdentityKeysetFile, createClusterWorkerCredentialIdentityKeysetFile, createClusterAutomationIdentityKeysetFile, createClusterApprovalIdentityKeysetFile, createClusterModelProviderCredentialIdentityKeysetFile, } = require('@qinglong/cluster-admin/plugin-package-identity-keyset'); const NOW_MS = 1_700_000_000_000; const ISSUER = 'https://identity.example.test/'; const AUDIENCE = 'qinglong3-package-management'; function reviewedKey(kid) { const { privateKey, publicKey } = generateKeyPairSync('ed25519'); return { kid, privateKey, publicJwk: { ...publicKey.export({ format: 'jwk' }), alg: 'EdDSA', kid, use: 'sig', }, }; } function keyset(generation, keys, revokedKids = []) { return { schemaVersion: 1, generation, issuer: ISSUER, audience: AUDIENCE, keys: keys.map((key) => key.publicJwk), revokedKids, assuranceMappings: [ { acr: 'urn:ql3:mfa', assurance: 'multi_factor', requiredAmr: ['pwd', 'otp'], }, ], constraints: { maxAssertionBytes: 8 * 1024, maxLifetimeMs: 5 * 60 * 1000, maxAuthenticationAgeMs: 5 * 60 * 1000, clockSkewMs: 5 * 1000, }, }; } function assertion(key, overrides = {}) { const header = Buffer.from( JSON.stringify({ alg: 'EdDSA', kid: key.kid, typ: 'ql3-plugin-package-management+jwt', }), ).toString('base64url'); const now = Math.floor(NOW_MS / 1000); const payload = Buffer.from( JSON.stringify({ acr: 'urn:ql3:mfa', amr: ['pwd', 'otp'], aud: AUDIENCE, auth_time: now - 10, exp: now + 120, iat: now, iss: ISSUER, jti: `assertion-${key.kid}`, ql3_purpose: 'plugin-package-management', sub: 'user-1', ...overrides, }), ).toString('base64url'); const signed = `${header}.${payload}`; return `${signed}.${sign( null, Buffer.from(signed, 'ascii'), key.privateKey, ).toString('base64url')}`; } function workerAssertion(key, overrides = {}) { const header = Buffer.from( JSON.stringify({ alg: 'EdDSA', kid: key.kid, typ: 'ql3-worker-credential-management+jwt', }), ).toString('base64url'); const now = Math.floor(NOW_MS / 1000); const payload = Buffer.from( JSON.stringify({ acr: 'urn:ql3:mfa', amr: ['pwd', 'otp'], aud: 'qinglong3-worker-credential-management', auth_time: now - 10, exp: now + 120, iat: now, iss: ISSUER, jti: `worker-assertion-${key.kid}`, ql3_purpose: 'worker-credential-management', sub: 'worker-operator-1', ...overrides, }), ).toString('base64url'); const signed = `${header}.${payload}`; return `${signed}.${sign( null, Buffer.from(signed, 'ascii'), key.privateKey, ).toString('base64url')}`; } function automationAssertion(key, overrides = {}) { const header = Buffer.from( JSON.stringify({ alg: 'EdDSA', kid: key.kid, typ: 'ql3-automation-management+jwt', }), ).toString('base64url'); const now = Math.floor(NOW_MS / 1000); const payload = Buffer.from( JSON.stringify({ acr: 'urn:ql3:mfa', amr: ['pwd', 'otp'], aud: 'qinglong3-automation-management', auth_time: now - 10, exp: now + 120, iat: now, iss: ISSUER, jti: `automation-assertion-${key.kid}`, ql3_purpose: 'automation-management', sub: 'automation-operator-1', ...overrides, }), ).toString('base64url'); const signed = `${header}.${payload}`; return `${signed}.${sign( null, Buffer.from(signed, 'ascii'), key.privateKey, ).toString('base64url')}`; } function approvalAssertion(key, overrides = {}) { const header = Buffer.from( JSON.stringify({ alg: 'EdDSA', kid: key.kid, typ: 'ql3-approval-management+jwt', }), ).toString('base64url'); const now = Math.floor(NOW_MS / 1000); const payload = Buffer.from( JSON.stringify({ acr: 'urn:ql3:mfa', amr: ['pwd', 'otp'], aud: 'qinglong3-approval-management', auth_time: now - 10, exp: now + 120, iat: now, iss: ISSUER, jti: `approval-assertion-${key.kid}`, ql3_purpose: 'approval-management', sub: 'approval-owner-1', ...overrides, }), ).toString('base64url'); const signed = `${header}.${payload}`; return `${signed}.${sign( null, Buffer.from(signed, 'ascii'), key.privateKey, ).toString('base64url')}`; } function providerCredentialAssertion(key, overrides = {}) { const header = Buffer.from( JSON.stringify({ alg: 'EdDSA', kid: key.kid, typ: 'ql3-model-provider-credential-management+jwt', }), ).toString('base64url'); const now = Math.floor(NOW_MS / 1000); const payload = Buffer.from( JSON.stringify({ acr: 'urn:ql3:mfa', amr: ['pwd', 'otp'], aud: 'qinglong3-model-provider-credential-management', auth_time: now - 10, exp: now + 120, iat: now, iss: ISSUER, jti: `provider-credential-assertion-${key.kid}`, ql3_purpose: 'model-provider-credential-management', sub: 'provider-credential-operator-1', ...overrides, }), ).toString('base64url'); const signed = `${header}.${payload}`; return `${signed}.${sign( null, Buffer.from(signed, 'ascii'), key.privateKey, ).toString('base64url')}`; } async function atomicWrite(filePath, document) { const nextPath = `${filePath}.next`; await writeFile(nextPath, `${JSON.stringify(document)}\n`, { mode: 0o644 }); await rename(nextPath, filePath); } async function fixture(run) { const directory = await mkdtemp(join(tmpdir(), 'ql3-identity-keyset-')); const filePath = join(directory, 'keyset.json'); try { return await run({ directory, filePath }); } finally { await rm(directory, { recursive: true, force: true }); } } test('loads one bounded keyset and authenticates through the current generation', async () => { await fixture(async ({ filePath }) => { const first = reviewedKey('issuer-key-1'); await atomicWrite(filePath, keyset(1, [first])); const provider = createClusterPluginPackageIdentityKeysetFile({ filePath, now: () => NOW_MS, }); assert.deepEqual(await provider.reload(), { schemaVersion: 1, generation: 1, digest: (await provider.reload()).digest, issuer: ISSUER, audience: AUDIENCE, activeKeyIds: ['issuer-key-1'], revokedKeyIds: [], }); const principal = await provider.bind(assertion(first)).authenticate(); assert.deepEqual(principal.subject, { type: 'user', id: 'user-1' }); assert.equal(principal.assurance, 'multi_factor'); }); }); test('loads a Worker credential keyset with a distinct assertion purpose', async () => { await fixture(async ({ filePath }) => { const key = reviewedKey('worker-identity-key-1'); await atomicWrite(filePath, { ...keyset(1, [key]), audience: 'qinglong3-worker-credential-management', }); const provider = createClusterWorkerCredentialIdentityKeysetFile({ filePath, now: () => NOW_MS, }); const principal = await provider.bind(workerAssertion(key)).authenticate(); assert.deepEqual(principal.subject, { type: 'user', id: 'worker-operator-1', }); await assert.rejects( provider .bind( assertion(key, { aud: 'qinglong3-worker-credential-management', }), ) .authenticate(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_ASSERTION_INVALID' }, ); }); }); test('loads an automation keyset with a purpose isolated from other management planes', async () => { await fixture(async ({ filePath }) => { const key = reviewedKey('automation-identity-key-1'); await atomicWrite(filePath, { ...keyset(1, [key]), audience: 'qinglong3-automation-management', }); const provider = createClusterAutomationIdentityKeysetFile({ filePath, now: () => NOW_MS, }); const principal = await provider .bind(automationAssertion(key)) .authenticate(); assert.deepEqual(principal.subject, { type: 'user', id: 'automation-operator-1', }); await assert.rejects( provider.bind(workerAssertion(key)).authenticate(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_ASSERTION_INVALID' }, ); await assert.rejects(provider.bind(assertion(key)).authenticate(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_ASSERTION_INVALID', }); }); }); test('loads an Approval keyset isolated by type, purpose and audience', async () => { await fixture(async ({ filePath }) => { const key = reviewedKey('approval-identity-key-1'); await atomicWrite(filePath, { ...keyset(1, [key]), audience: 'qinglong3-approval-management', }); const provider = createClusterApprovalIdentityKeysetFile({ filePath, now: () => NOW_MS, }); const principal = await provider.bind(approvalAssertion(key)).authenticate(); assert.deepEqual(principal.subject, { type: 'user', id: 'approval-owner-1', }); await assert.rejects(provider.bind(automationAssertion(key)).authenticate(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_ASSERTION_INVALID', }); await assert.rejects(provider.bind(assertion(key)).authenticate(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_ASSERTION_INVALID', }); }); }); test('loads a provider credential keyset isolated by type, purpose and audience', async () => { await fixture(async ({ filePath }) => { const key = reviewedKey('provider-credential-identity-key-1'); await atomicWrite(filePath, { ...keyset(1, [key]), audience: 'qinglong3-model-provider-credential-management', }); const provider = createClusterModelProviderCredentialIdentityKeysetFile({ filePath, now: () => NOW_MS, }); const principal = await provider .bind(providerCredentialAssertion(key)) .authenticate(); assert.deepEqual(principal.subject, { type: 'user', id: 'provider-credential-operator-1', }); await assert.rejects( provider.bind(automationAssertion(key)).authenticate(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_ASSERTION_INVALID' }, ); await assert.rejects( provider .bind( providerCredentialAssertion(key, { ql3_purpose: 'automation-management', }), ) .authenticate(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_ASSERTION_INVALID' }, ); }); }); test('supports overlap rotation then immediately revokes the previous key', async () => { await fixture(async ({ filePath }) => { const first = reviewedKey('issuer-key-1'); const second = reviewedKey('issuer-key-2'); await atomicWrite(filePath, keyset(1, [first])); const provider = createClusterPluginPackageIdentityKeysetFile({ filePath, now: () => NOW_MS, }); await provider.reload(); await atomicWrite(filePath, keyset(2, [first, second])); assert.deepEqual((await provider.reload()).activeKeyIds, [ 'issuer-key-1', 'issuer-key-2', ]); assert.equal( (await provider.bind(assertion(second)).authenticate()).subject.id, 'user-1', ); await atomicWrite(filePath, keyset(3, [first, second], ['issuer-key-1'])); assert.deepEqual(await provider.reload(), { schemaVersion: 1, generation: 3, digest: (await provider.reload()).digest, issuer: ISSUER, audience: AUDIENCE, activeKeyIds: ['issuer-key-2'], revokedKeyIds: ['issuer-key-1'], }); await assert.rejects(provider.bind(assertion(first)).authenticate(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_ASSERTION_INVALID', }); assert.equal( (await provider.bind(assertion(second)).authenticate()).subject.id, 'user-1', ); }); }); test('rejects generation rollback, same-generation rewrite and implicit removal', async () => { await fixture(async ({ filePath }) => { const first = reviewedKey('issuer-key-1'); const second = reviewedKey('issuer-key-2'); await atomicWrite(filePath, keyset(1, [first])); const provider = createClusterPluginPackageIdentityKeysetFile({ filePath, now: () => NOW_MS, }); await provider.reload(); await atomicWrite(filePath, keyset(1, [first, second])); await assert.rejects(provider.reload(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_KEYSET_UNAVAILABLE', }); await atomicWrite(filePath, keyset(2, [first, second])); await provider.reload(); await atomicWrite(filePath, keyset(3, [second])); await assert.rejects(provider.reload(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_KEYSET_UNAVAILABLE', }); await atomicWrite(filePath, keyset(1, [first])); await assert.rejects(provider.reload(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_KEYSET_UNAVAILABLE', }); }); }); test('keeps revocation append-only and rejects stale fallback on file failure', async () => { await fixture(async ({ filePath }) => { const first = reviewedKey('issuer-key-1'); const second = reviewedKey('issuer-key-2'); await atomicWrite(filePath, keyset(1, [first, second])); const provider = createClusterPluginPackageIdentityKeysetFile({ filePath, now: () => NOW_MS, }); await provider.reload(); await atomicWrite(filePath, keyset(2, [first, second], ['issuer-key-1'])); await provider.reload(); await atomicWrite(filePath, keyset(3, [first, second])); await assert.rejects(provider.reload(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_KEYSET_UNAVAILABLE', }); await atomicWrite(filePath, keyset(3, [first, second], ['issuer-key-1'])); await chmod(filePath, 0o666); await assert.rejects( provider.bind(assertion(second)).authenticate(), ClusterPluginPackageIdentityKeysetUnavailableError, ); }); }); test('rechecks one durable ledger across unchanged files and fresh replicas', async () => { await fixture(async ({ filePath }) => { const first = reviewedKey('issuer-key-1'); const second = reviewedKey('issuer-key-2'); let minimumGeneration = 1; const observations = []; const ledger = { async observe(snapshot) { observations.push(snapshot); if (snapshot.generation < minimumGeneration) { throw new Error('durable generation rollback'); } minimumGeneration = snapshot.generation; }, }; await atomicWrite(filePath, keyset(1, [first])); const firstReplica = createClusterPluginPackageIdentityKeysetFile({ filePath, now: () => NOW_MS, ledger, }); await firstReplica.reload(); await firstReplica.reload(); assert.equal(observations.length, 2); await atomicWrite(filePath, keyset(2, [first, second], ['issuer-key-1'])); await firstReplica.reload(); assert.equal(minimumGeneration, 2); await atomicWrite(filePath, keyset(1, [first])); const restartedReplica = createClusterPluginPackageIdentityKeysetFile({ filePath, now: () => NOW_MS, ledger, }); await assert.rejects( restartedReplica.reload(), ClusterPluginPackageIdentityKeysetUnavailableError, ); await atomicWrite(filePath, keyset(2, [first, second], ['issuer-key-1'])); minimumGeneration = 3; await assert.rejects( firstReplica.bind(assertion(second)).authenticate(), ClusterPluginPackageIdentityKeysetUnavailableError, ); }); }); test('rejects malformed, private and oversized trust documents', async () => { await fixture(async ({ filePath }) => { const first = reviewedKey('issuer-key-1'); const provider = createClusterPluginPackageIdentityKeysetFile({ filePath, maxFileBytes: 4 * 1024, now: () => NOW_MS, }); await atomicWrite(filePath, { ...keyset(1, [first]), keys: [{ ...first.publicJwk, d: 'private' }], }); await assert.rejects(provider.reload(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_KEYSET_UNAVAILABLE', }); await writeFile(filePath, Buffer.alloc(4 * 1024 + 1, 0x20), { mode: 0o644, }); await assert.rejects(provider.reload(), { code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_KEYSET_UNAVAILABLE', }); }); });