feat(ql3): cut alpha.1 candidate milestone

This commit is contained in:
whyour
2026-08-26 01:41:20 +08:00
parent c2df0c7215
commit 07cdc76bae
94 changed files with 1469 additions and 168 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@qinglong/cluster-admin",
"version": "3.0.0-alpha.0",
"version": "3.0.0-alpha.1",
"private": true,
"description": "QingLong 3.0 cluster operations and bounded Copilot MCP/Console surfaces",
"license": "Apache-2.0",
@@ -3,6 +3,8 @@ import type {
PostgresDatabaseResource,
} from '@qinglong/runtime-core';
import { assertApiCredentialPepper } from '@qinglong/runtime-core/api-credential-token';
import { createSingletonApiCredentialPepperKeyring } from '@qinglong/runtime-core/api-credential-pepper-keyring';
import { LEGACY_API_CREDENTIAL_PEPPER_KEY_ID } from '@qinglong/runtime-core/api-credential';
import { assertWorkerCredentialPepper } from '@qinglong/runtime-core/worker-credential-token';
import type { SecurityAuditQueryRepository } from '@qinglong/runtime-core/security-audit-query';
import {
@@ -97,7 +99,10 @@ export async function bootstrapClusterAdmin(
administration: createClusterAdministrationService(
identities,
credentials,
options.apiCredentialPepper,
createSingletonApiCredentialPepperKeyring(
options.apiCredentialPepper,
LEGACY_API_CREDENTIAL_PEPPER_KEY_ID,
).keys[0]!,
{
...(options.now ? { now: options.now } : {}),
...(options.randomBytes ? { randomBytes: options.randomBytes } : {}),
@@ -13,9 +13,11 @@ import {
formatApiCredentialToken,
} from '@qinglong/runtime-core/api-credential-token';
import {
LEGACY_API_CREDENTIAL_PEPPER_KEY_ID,
assertApiCredentialPepperKeyId,
type ApiCredentialRecord,
LEGACY_API_CREDENTIAL_PEPPER_KEY_ID,
} from '@qinglong/runtime-core/api-credential';
import type { ApiCredentialPepperKey } from '@qinglong/runtime-core/api-credential-pepper-keyring';
import {
type AppendIdentitySubjectResult,
IdentityAdministrationMutationConflictError,
@@ -283,7 +285,7 @@ function sameCredentialReplay(
export function createClusterAdministrationService(
identities: IdentityAdministrationRepository,
credentials: ApiCredentialAdministrationRepository,
pepper: string,
activePepperKeyValue: Readonly<ApiCredentialPepperKey> | string,
options: ClusterAdministrationOptions = {},
): ClusterAdministrationService {
if (
@@ -305,10 +307,28 @@ export function createClusterAdministrationService(
'credential repository is invalid',
);
}
const activePepperKey =
typeof activePepperKeyValue === 'string'
? Object.freeze({
pepperKeyId: LEGACY_API_CREDENTIAL_PEPPER_KEY_ID,
pepper: activePepperKeyValue,
})
: activePepperKeyValue;
try {
assertApiCredentialPepper(pepper);
if (
!activePepperKey ||
typeof activePepperKey !== 'object' ||
Array.isArray(activePepperKey) ||
Object.keys(activePepperKey).sort().join(',') !== 'pepper,pepperKeyId'
) {
throw new TypeError();
}
assertApiCredentialPepper(activePepperKey.pepper);
assertApiCredentialPepperKeyId(activePepperKey.pepperKeyId);
} catch {
throw new ClusterAdministrationConfigurationError('pepper is invalid');
throw new ClusterAdministrationConfigurationError(
'active pepper key is invalid',
);
}
exactObject(options, 'options');
const optionKeys = Object.keys(options);
@@ -442,7 +462,7 @@ export function createClusterAdministrationService(
try {
secretBase64Url = secret.toString('base64url');
secretDigest = apiCredentialSecretDigest(
pepper,
activePepperKey.pepper,
request.credentialId,
secretBase64Url,
);
@@ -454,7 +474,7 @@ export function createClusterAdministrationService(
const credential: ApiCredentialRecord = {
credentialId: request.credentialId,
version: request.expectedCurrentVersion + 1,
pepperKeyId: LEGACY_API_CREDENTIAL_PEPPER_KEY_ID,
pepperKeyId: activePepperKey.pepperKeyId,
state: operation === 'revoke' ? 'revoked' : 'active',
subject: request.subject,
subjectStatus: identity.status,
@@ -6,7 +6,7 @@ import {
} from './clusterAdministrationCommand';
const USAGE =
'Usage: ql3-security-admin --command=/absolute/command.json --assertion=/absolute/assertion.jwt --keyset=/absolute/keyset.json --pepper=/absolute/pepper [--delivery=/absolute/token.json]';
'Usage: ql3-security-admin --command=/absolute/command.json --assertion=/absolute/assertion.jwt --keyset=/absolute/keyset.json (--pepper=/absolute/pepper | --pepper-keyring=/absolute/keyring.json) [--delivery=/absolute/token.json]';
function argumentsFrom(argv: readonly string[]) {
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
@@ -14,9 +14,10 @@ function argumentsFrom(argv: readonly string[]) {
}
const values = new Map<string, string>();
for (const argument of argv) {
const match = /^--(command|assertion|keyset|pepper|delivery)=(\/.+)$/.exec(
argument,
);
const match =
/^--(command|assertion|keyset|pepper|pepper-keyring|delivery)=(\/.+)$/.exec(
argument,
);
if (!match || values.has(match[1]!)) {
throw new ClusterAdministrationCommandError('CLI arguments are invalid');
}
@@ -26,7 +27,7 @@ function argumentsFrom(argv: readonly string[]) {
!values.has('command') ||
!values.has('assertion') ||
!values.has('keyset') ||
!values.has('pepper')
values.has('pepper') === values.has('pepper-keyring')
) {
throw new ClusterAdministrationCommandError('CLI arguments are invalid');
}
@@ -36,7 +37,9 @@ function argumentsFrom(argv: readonly string[]) {
commandFile: values.get('command')!,
assertionFile: values.get('assertion')!,
keysetFile: values.get('keyset')!,
pepperFile: values.get('pepper')!,
...(values.has('pepper')
? { pepperFile: values.get('pepper')! }
: { pepperKeyringFile: values.get('pepper-keyring')! }),
...(values.has('delivery')
? { deliveryFile: values.get('delivery')! }
: {}),
@@ -1,7 +1,17 @@
import { createHash } from 'node:crypto';
import { basename } from 'node:path';
import { assertApiCredentialPepper } from '@qinglong/runtime-core/api-credential-token';
import {
createSingletonApiCredentialPepperKeyring,
normalizeApiCredentialPepperKeyring,
type ApiCredentialPepperKeyring,
} from '@qinglong/runtime-core/api-credential-pepper-keyring';
import { LEGACY_API_CREDENTIAL_PEPPER_KEY_ID } from '@qinglong/runtime-core/api-credential';
import {
normalizeApiCredentialPepperReferenceKeyId,
normalizeApiCredentialPepperReferenceLimit,
type ApiCredentialPepperReferenceRepository,
} from '@qinglong/runtime-core/api-credential-pepper-reference';
import { normalizeIdentityAdministrationSubject } from '@qinglong/runtime-core/identity-administration';
import {
normalizeSecurityAuditQuery,
@@ -34,6 +44,7 @@ const MAX_VERSION = 2_147_483_646;
const MAX_COMMAND_BYTES = 64 * 1024;
const MAX_ASSERTION_BYTES = 16 * 1024;
const MAX_PEPPER_BYTES = 256;
const MAX_PEPPER_KEYRING_BYTES = 2 * 1024;
export type ClusterAdministrationCommandOperation =
| 'identity.register'
@@ -42,6 +53,7 @@ export type ClusterAdministrationCommandOperation =
| 'credential.issue'
| 'credential.rotate'
| 'credential.revoke'
| 'pepper.references'
| 'audit.list';
interface BaseMutationRequest {
@@ -79,16 +91,27 @@ interface AuditCommand {
readonly request: SecurityAuditQuery;
}
interface PepperReferenceCommand {
readonly schemaVersion: 1;
readonly operation: 'pepper.references';
readonly request: Readonly<{
readonly pepperKeyId: string;
readonly limit: number;
}>;
}
export type ClusterAdministrationCommand =
| IdentityCommand
| CredentialCommand
| PepperReferenceCommand
| AuditCommand;
export interface ClusterAdministrationCommandPaths {
readonly commandFile: string;
readonly assertionFile: string;
readonly keysetFile: string;
readonly pepperFile: string;
readonly pepperFile?: string;
readonly pepperKeyringFile?: string;
readonly deliveryFile?: string;
}
@@ -115,18 +138,27 @@ export type ClusterAdministrationCommandResult =
schemaVersion: 1;
operation: 'audit.list';
page: Readonly<SecurityAuditQueryPage>;
}>
| Readonly<{
schemaVersion: 1;
operation: 'pepper.references';
pepperKeyId: string;
observedAtMs: number;
credentialIds: readonly string[];
hasMore: boolean;
}>;
export interface ClusterAdministrationCommandAuthority {
readonly administration: ClusterAdministrationService;
readonly audit: SecurityAuditQueryRepository;
readonly pepperReferences: ApiCredentialPepperReferenceRepository;
close(): Promise<void>;
}
export interface ClusterAdministrationCommandDependencies {
readonly openAuthority: (
environment: Readonly<Record<string, string | undefined>>,
pepper: string,
pepperKeyring: Readonly<ApiCredentialPepperKeyring>,
) => Promise<Readonly<ClusterAdministrationCommandAuthority>>;
readonly authenticate: (
keysetFile: string,
@@ -243,6 +275,7 @@ export function normalizeClusterAdministrationCommand(
'credential.issue',
'credential.rotate',
'credential.revoke',
'pepper.references',
'audit.list',
];
if (
@@ -257,6 +290,28 @@ export function normalizeClusterAdministrationCommand(
);
}
const operation = value.operation as ClusterAdministrationCommandOperation;
if (operation === 'pepper.references') {
exactObject(value.request, ['limit', 'pepperKeyId'], 'pepper reference');
try {
return Object.freeze({
schemaVersion: 1 as const,
operation,
request: Object.freeze({
pepperKeyId: normalizeApiCredentialPepperReferenceKeyId(
value.request.pepperKeyId as string,
),
limit: normalizeApiCredentialPepperReferenceLimit(
value.request.limit as number,
),
}),
});
} catch (error) {
throw new ClusterAdministrationCommandError(
'pepper reference query is invalid',
error,
);
}
}
if (operation === 'audit.list') {
let request: Readonly<SecurityAuditQuery>;
try {
@@ -370,9 +425,11 @@ export function createClusterAdministrationCommandRunner(
MAX_ASSERTION_BYTES,
true,
);
const pepperBytes = dependencies.readFile(
paths.pepperFile,
MAX_PEPPER_BYTES,
const pepperKeyringBytes = dependencies.readFile(
paths.pepperKeyringFile ?? paths.pepperFile!,
paths.pepperKeyringFile === undefined
? MAX_PEPPER_BYTES
: MAX_PEPPER_KEYRING_BYTES,
true,
);
let authority:
@@ -380,13 +437,46 @@ export function createClusterAdministrationCommandRunner(
| undefined;
try {
const assertion = strictUtf8(assertionBytes, 'assertion file').trim();
const pepper = strictUtf8(pepperBytes, 'pepper file').trim();
assertApiCredentialPepper(pepper);
let pepperKeyring: Readonly<ApiCredentialPepperKeyring>;
if (paths.pepperKeyringFile === undefined) {
pepperKeyring = createSingletonApiCredentialPepperKeyring(
strictUtf8(pepperKeyringBytes, 'pepper file').trim(),
LEGACY_API_CREDENTIAL_PEPPER_KEY_ID,
);
} else {
try {
pepperKeyring = normalizeApiCredentialPepperKeyring(
JSON.parse(
strictUtf8(pepperKeyringBytes, 'pepper keyring file'),
),
);
} catch (error) {
throw new ClusterAdministrationCommandError(
'pepper keyring file is invalid',
error,
);
}
}
const principal = await dependencies.authenticate(
paths.keysetFile,
assertion,
);
authority = await dependencies.openAuthority(environment, pepper);
authority = await dependencies.openAuthority(
environment,
pepperKeyring,
);
if (command.operation === 'pepper.references') {
void principal;
const inspection = await authority.pepperReferences.inspect(
command.request.pepperKeyId,
command.request.limit,
);
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
...inspection,
});
}
if (command.operation === 'audit.list') {
// Successful verification is the short-lived admin admission. Audit
// queries remain read-only and use the repository's bounded contract.
@@ -460,7 +550,7 @@ export function createClusterAdministrationCommandRunner(
});
} finally {
assertionBytes.fill(0);
pepperBytes.fill(0);
pepperKeyringBytes.fill(0);
await authority?.close();
}
},
@@ -20,9 +20,13 @@ import {
resolve,
} from 'node:path';
import { assertApiCredentialPepper } from '@qinglong/runtime-core/api-credential-token';
import {
activeApiCredentialPepperKey,
type ApiCredentialPepperKeyring,
} from '@qinglong/runtime-core/api-credential-pepper-keyring';
import {
PostgresApiCredentialAdministrationRepository,
PostgresApiCredentialPepperReferenceRepository,
PostgresIdentityAdministrationRepository,
PostgresSecurityAuditQueryRepository,
assertPostgresAdminSchemaReady,
@@ -249,9 +253,9 @@ function defaultDatabaseOpener(
async function openDefaultAuthority(
environment: Readonly<Record<string, string | undefined>>,
pepper: string,
pepperKeyring: Readonly<ApiCredentialPepperKeyring>,
): Promise<Readonly<ClusterAdministrationCommandAuthority>> {
assertApiCredentialPepper(pepper);
const activePepperKey = activeApiCredentialPepperKey(pepperKeyring);
const database = await defaultDatabaseOpener(environment)();
let closePromise: Promise<void> | undefined;
const close = (): Promise<void> => {
@@ -264,9 +268,12 @@ async function openDefaultAuthority(
administration: createClusterAdministrationService(
new PostgresIdentityAdministrationRepository(database.pool),
new PostgresApiCredentialAdministrationRepository(database.pool),
pepper,
activePepperKey,
),
audit: new PostgresSecurityAuditQueryRepository(database.pool),
pepperReferences: new PostgresApiCredentialPepperReferenceRepository(
database.pool,
),
close,
});
} catch (error) {
@@ -388,11 +395,20 @@ export function normalizeClusterAdministrationCommandPaths(
'command paths must be an object',
);
}
const pepperPathKeys = [
value.pepperFile === undefined ? null : 'pepperFile',
value.pepperKeyringFile === undefined ? null : 'pepperKeyringFile',
].filter((key): key is string => key !== null);
if (pepperPathKeys.length !== 1) {
throw new ClusterAdministrationCommandError(
'exactly one pepper source is required',
);
}
const expected = [
'assertionFile',
'commandFile',
'keysetFile',
'pepperFile',
pepperPathKeys[0]!,
...(requiresDelivery ? ['deliveryFile'] : []),
].sort();
const actual = Object.keys(value).sort();
@@ -417,10 +433,19 @@ export function normalizeClusterAdministrationCommandPaths(
value.keysetFile,
'identity keyset file',
),
pepperFile: boundedClusterAdministrationFile(
value.pepperFile,
'pepper file',
),
...(value.pepperFile === undefined
? {
pepperKeyringFile: boundedClusterAdministrationFile(
value.pepperKeyringFile,
'pepper keyring file',
),
}
: {
pepperFile: boundedClusterAdministrationFile(
value.pepperFile,
'pepper file',
),
}),
...(requiresDelivery
? {
deliveryFile: boundedClusterAdministrationFile(
@@ -441,11 +466,18 @@ export function clusterAdministrationCommandFileBeforeAdmission(
);
}
const candidate = value as Record<string, unknown>;
const required = ['assertionFile', 'commandFile', 'keysetFile', 'pepperFile'];
const required = ['assertionFile', 'commandFile', 'keysetFile'];
const pepperSources = ['pepperFile', 'pepperKeyringFile'].filter((key) =>
Object.hasOwn(candidate, key),
);
if (
required.some((key) => !Object.hasOwn(candidate, key)) ||
pepperSources.length !== 1 ||
Object.keys(candidate).some(
(key) => !required.includes(key) && key !== 'deliveryFile',
(key) =>
!required.includes(key) &&
!pepperSources.includes(key) &&
key !== 'deliveryFile',
)
) {
throw new ClusterAdministrationCommandError(
@@ -122,6 +122,29 @@ test('issues one token, stores only its digest and clears mutable secret bytes',
);
});
test('binds newly issued credentials to the selected active pepper key', async () => {
const repos = repositories();
const nextPepper = Buffer.alloc(32, 2).toString('base64url');
const generated = Buffer.alloc(32, 9);
const service = createClusterAdministrationService(
repos.identities,
repos.credentials,
{ pepperKeyId: 'rotation-2026-08', pepper: nextPepper },
{ now: () => NOW, randomBytes: () => generated },
);
const result = await service.issueCredential(request());
const secret = result.token.split('_').at(-1);
assert.equal(
repos.credentialCommands[0].credential.pepperKeyId,
'rotation-2026-08',
);
assert.equal(
repos.credentialCommands[0].credential.secretDigest,
apiCredentialSecretDigest(nextPepper, 'credential_primary', secret),
);
});
test('semantic mutation replay returns no token and does not generate a new secret', async () => {
const repos = repositories();
let randomCalls = 0;
@@ -114,6 +114,17 @@ function authority(overrides = {}) {
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;
},
@@ -175,7 +186,11 @@ test('executes one strongly authenticated identity mutation and closes authority
assertion: 'signed.assertion.value',
},
]);
assert.equal(execution.opens[0].pepper, 'A'.repeat(43));
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(
@@ -312,6 +327,73 @@ test('keeps audit query bounded and rejects widened command shapes before admiss
);
});
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({