feat(ql3): add short-lived security administration command

This commit is contained in:
whyour
2026-08-25 02:09:06 +08:00
parent 9c736f0943
commit 7b3d49acda
17 changed files with 1756 additions and 17 deletions
@@ -13,6 +13,7 @@ const {
createClusterApprovalIdentityKeysetFile,
createClusterModelProviderCredentialIdentityKeysetFile,
createClusterRunIdentityKeysetFile,
createClusterSecurityAdministrationIdentityKeysetFile,
} = require('@qinglong/cluster-admin/plugin-package-identity-keyset');
const NOW_MS = 1_700_000_000_000;
@@ -249,6 +250,38 @@ function runAssertion(key, overrides = {}) {
).toString('base64url')}`;
}
function securityAdministrationAssertion(key, overrides = {}) {
const header = Buffer.from(
JSON.stringify({
alg: 'EdDSA',
kid: key.kid,
typ: 'ql3-security-administration+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-security-administration',
auth_time: now - 10,
exp: now + 120,
iat: now,
iss: ISSUER,
jti: `security-administration-assertion-${key.kid}`,
ql3_purpose: 'security-administration',
sub: 'security-owner-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 });
@@ -337,10 +370,9 @@ test('loads an automation keyset with a purpose isolated from other management p
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(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',
});
@@ -358,14 +390,19 @@ test('loads an Approval keyset isolated by type, purpose and audience', async ()
filePath,
now: () => NOW_MS,
});
const principal = await provider.bind(approvalAssertion(key)).authenticate();
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(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',
});
@@ -435,6 +472,40 @@ test('loads a Run keyset isolated from every other management purpose', async ()
});
});
test('loads a Security Administration keyset isolated from other management purposes', async () => {
await fixture(async ({ filePath }) => {
const key = reviewedKey('security-administration-key-1');
await atomicWrite(filePath, {
...keyset(1, [key]),
audience: 'qinglong3-security-administration',
});
const provider = createClusterSecurityAdministrationIdentityKeysetFile({
filePath,
now: () => NOW_MS,
});
const principal = await provider
.bind(securityAdministrationAssertion(key))
.authenticate();
assert.deepEqual(principal.subject, {
type: 'user',
id: 'security-owner-1',
});
await assert.rejects(provider.bind(runAssertion(key)).authenticate(), {
code: 'CLUSTER_PLUGIN_PACKAGE_IDENTITY_ASSERTION_INVALID',
});
await assert.rejects(
provider
.bind(
securityAdministrationAssertion(key, {
ql3_purpose: 'run-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');