mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,506 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
PluginPackageManagementLiveExerciseError,
|
||||
assertionIdentity,
|
||||
beforePhase,
|
||||
exerciseFromState,
|
||||
ingressProbes,
|
||||
overlapPhase,
|
||||
probePod,
|
||||
revokedPhase,
|
||||
stateDigest,
|
||||
validateKeyset,
|
||||
validateState,
|
||||
} = require('../../scripts/ql3-plugin-package-management-live-exercise.cjs');
|
||||
const {
|
||||
validateExercise,
|
||||
} = require('../../scripts/ql3-plugin-package-management-live-evidence-collect.cjs');
|
||||
|
||||
const NOW = Date.parse('2026-07-25T12:00:00.000Z');
|
||||
const ISSUER = 'https://login.example.com/';
|
||||
const AUDIENCE = 'qinglong3-package-management';
|
||||
|
||||
function compact(value) {
|
||||
return Buffer.from(JSON.stringify(value)).toString('base64url');
|
||||
}
|
||||
|
||||
function assertion({ kid, subject, jti, assurance = 'mfa', now = NOW }) {
|
||||
const seconds = Math.floor(now / 1000);
|
||||
return `${compact({
|
||||
alg: 'EdDSA',
|
||||
kid,
|
||||
typ: 'ql3-plugin-package-management+jwt',
|
||||
})}.${compact({
|
||||
acr: assurance,
|
||||
amr: assurance === 'mfa' ? ['pwd', 'otp'] : ['hwk'],
|
||||
aud: AUDIENCE,
|
||||
auth_time: seconds - 10,
|
||||
exp: seconds + 120,
|
||||
iat: seconds - 10,
|
||||
iss: ISSUER,
|
||||
jti,
|
||||
ql3_purpose: 'plugin-package-management',
|
||||
sub: subject,
|
||||
})}.c2lnbmF0dXJl`;
|
||||
}
|
||||
|
||||
function keyset(generation, activeKids, revokedKids = []) {
|
||||
const document = {
|
||||
schemaVersion: 1,
|
||||
generation,
|
||||
issuer: ISSUER,
|
||||
audience: AUDIENCE,
|
||||
keys: [...new Set([...activeKids, ...revokedKids])].map((kid) => ({
|
||||
alg: 'EdDSA',
|
||||
crv: 'Ed25519',
|
||||
kid,
|
||||
kty: 'OKP',
|
||||
use: 'sig',
|
||||
x: 'a'.repeat(43),
|
||||
})),
|
||||
revokedKids,
|
||||
assuranceMappings: [
|
||||
{ acr: 'mfa', assurance: 'multi_factor', requiredAmr: ['pwd', 'otp'] },
|
||||
{ acr: 'hardware', assurance: 'hardware', requiredAmr: ['hwk'] },
|
||||
],
|
||||
constraints: {
|
||||
maxAssertionBytes: 8192,
|
||||
maxLifetimeMs: 300000,
|
||||
maxAuthenticationAgeMs: 300000,
|
||||
clockSkewMs: 5000,
|
||||
},
|
||||
};
|
||||
return {
|
||||
...validateKeyset(document),
|
||||
resourceVersion: `keyset-${generation}`,
|
||||
};
|
||||
}
|
||||
|
||||
function snapshot(generation, activeKids, revokedKids, tls) {
|
||||
return {
|
||||
clusterIdentitySha256: `sha256:${'1'.repeat(64)}`,
|
||||
replicas: 2,
|
||||
readyReplicas: 2,
|
||||
unavailableReplicas: 0,
|
||||
pods: [
|
||||
{ name: 'management-a', uid: 'pod-a', ip: '10.0.0.10' },
|
||||
{ name: 'management-b', uid: 'pod-b', ip: '10.0.0.11' },
|
||||
],
|
||||
image: `registry.example/qinglong3@sha256:${'2'.repeat(64)}`,
|
||||
imagePullPolicy: 'IfNotPresent',
|
||||
imagePullSecrets: [],
|
||||
keyset: keyset(generation, activeKids, revokedKids),
|
||||
tls: {
|
||||
serial: tls.serial,
|
||||
resourceVersion: tls.resourceVersion,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function http(snapshotValue, status, payload) {
|
||||
return {
|
||||
status,
|
||||
payload,
|
||||
tlsProtocol: 'TLSv1.3',
|
||||
tlsSerial: snapshotValue.tls.serial,
|
||||
};
|
||||
}
|
||||
|
||||
function success(snapshotValue, operation, extra = {}) {
|
||||
return http(snapshotValue, 200, {
|
||||
schemaVersion: 1,
|
||||
requestId: 'request-id',
|
||||
result: { schemaVersion: 1, operation, ...extra },
|
||||
});
|
||||
}
|
||||
|
||||
function failure(snapshotValue, status, code) {
|
||||
return http(snapshotValue, status, {
|
||||
schemaVersion: 1,
|
||||
requestId: 'request-id',
|
||||
error: { code },
|
||||
});
|
||||
}
|
||||
|
||||
function exactIsolation() {
|
||||
return {
|
||||
labelledClientOutcome: 'tls13_connected',
|
||||
unlabelledClientOutcome: 'timeout',
|
||||
wrongPortOutcome: 'timeout',
|
||||
kubernetesApiEgressOutcome: 'timeout',
|
||||
publicInternetEgressOutcome: 'timeout',
|
||||
postgresEgressOutcome: 'postgres_ready',
|
||||
};
|
||||
}
|
||||
|
||||
test('derives strong identities from the active keyset assurance mapping', () => {
|
||||
const reviewed = keyset(7, ['old-key']);
|
||||
const identity = assertionIdentity(
|
||||
assertion({
|
||||
kid: 'old-key',
|
||||
subject: 'tenant/requester',
|
||||
jti: 'requester-jti',
|
||||
}),
|
||||
reviewed,
|
||||
NOW,
|
||||
);
|
||||
assert.deepEqual(identity, {
|
||||
kid: 'old-key',
|
||||
subject: 'tenant/requester',
|
||||
assurance: 'multi_factor',
|
||||
issuer: ISSUER,
|
||||
audience: AUDIENCE,
|
||||
expiresAt: Math.floor(NOW / 1000) + 120,
|
||||
});
|
||||
assert.throws(
|
||||
() =>
|
||||
assertionIdentity(
|
||||
assertion({
|
||||
kid: 'old-key',
|
||||
subject: 'tenant/requester',
|
||||
jti: 'expired-jti',
|
||||
now: NOW - 10 * 60 * 1000,
|
||||
}),
|
||||
reviewed,
|
||||
NOW,
|
||||
),
|
||||
PluginPackageManagementLiveExerciseError,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects any private JWK material in the projected identity document', () => {
|
||||
const value = keyset(7, ['old-key']).document;
|
||||
const privateValue = structuredClone(value);
|
||||
privateValue.keys[0].d = 'private-component';
|
||||
assert.throws(
|
||||
() => validateKeyset(privateValue),
|
||||
PluginPackageManagementLiveExerciseError,
|
||||
);
|
||||
});
|
||||
|
||||
test('runs before, overlap and revoked as an ordered digest-linked ceremony', async () => {
|
||||
const requester = assertion({
|
||||
kid: 'old-key',
|
||||
subject: 'tenant/requester',
|
||||
jti: 'requester-jti',
|
||||
});
|
||||
const reviewer = assertion({
|
||||
kid: 'old-key',
|
||||
subject: 'tenant/reviewer',
|
||||
jti: 'reviewer-jti',
|
||||
});
|
||||
const oldOverlap = assertion({
|
||||
kid: 'old-key',
|
||||
subject: 'tenant/requester',
|
||||
jti: 'old-overlap-jti',
|
||||
now: NOW + 60_000,
|
||||
});
|
||||
const newOverlap = assertion({
|
||||
kid: 'new-key',
|
||||
subject: 'tenant/reviewer',
|
||||
jti: 'new-overlap-jti',
|
||||
now: NOW + 60_000,
|
||||
});
|
||||
const beforeSnapshot = snapshot(7, ['old-key'], [], {
|
||||
serial: 'A1',
|
||||
resourceVersion: 'tls-1',
|
||||
});
|
||||
let beforeRequest = 0;
|
||||
const before = await beforePhase(
|
||||
{
|
||||
endpoint: `https://management.example.com${'/api/v3/plugin-packages/management'}`,
|
||||
requesterAssertion: requester,
|
||||
reviewerAssertion: reviewer,
|
||||
actionInput: { evidence: true },
|
||||
nowMs: NOW,
|
||||
},
|
||||
{
|
||||
async snapshot() {
|
||||
return beforeSnapshot;
|
||||
},
|
||||
async request(assertionValue, command) {
|
||||
beforeRequest += 1;
|
||||
if (command.operation === 'plugin-package.propose') {
|
||||
assert.equal(assertionValue, requester);
|
||||
return success(beforeSnapshot, command.operation, {
|
||||
approval: { version: 1, state: 'pending' },
|
||||
});
|
||||
}
|
||||
if (
|
||||
command.operation === 'plugin-package.decide' &&
|
||||
assertionValue === requester
|
||||
) {
|
||||
return failure(beforeSnapshot, 403, 'forbidden');
|
||||
}
|
||||
if (command.operation === 'plugin-package.decide') {
|
||||
assert.equal(assertionValue, reviewer);
|
||||
return success(beforeSnapshot, command.operation, {
|
||||
approval: { version: 2, state: 'approved' },
|
||||
});
|
||||
}
|
||||
assert.equal(command.operation, 'plugin-package.inspect');
|
||||
return success(beforeSnapshot, command.operation, {
|
||||
approval: { version: 2, state: 'approved' },
|
||||
});
|
||||
},
|
||||
async network() {
|
||||
return exactIsolation();
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.equal(beforeRequest, 4);
|
||||
assert.equal(validateState(before, 'before'), before);
|
||||
|
||||
const overlapSnapshot = snapshot(8, ['old-key', 'new-key'], [], {
|
||||
serial: 'B2',
|
||||
resourceVersion: 'tls-2',
|
||||
});
|
||||
const overlap = await overlapPhase(
|
||||
{
|
||||
endpoint: before.endpoint,
|
||||
state: before,
|
||||
oldAssertion: oldOverlap,
|
||||
newAssertion: newOverlap,
|
||||
nowMs: NOW + 60_000,
|
||||
},
|
||||
{
|
||||
async snapshot() {
|
||||
return overlapSnapshot;
|
||||
},
|
||||
async request(_assertionValue, command) {
|
||||
return success(overlapSnapshot, command.operation, {
|
||||
approval: { version: 2, state: 'approved' },
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.equal(overlap.previousStateSha256, before.stateSha256);
|
||||
assert.equal(validateState(overlap, 'overlap'), overlap);
|
||||
await assert.rejects(
|
||||
() =>
|
||||
revokedPhase(
|
||||
{
|
||||
endpoint: overlap.endpoint,
|
||||
state: overlap,
|
||||
oldAssertion: assertion({
|
||||
kid: 'old-key',
|
||||
subject: 'tenant/requester',
|
||||
jti: 'replacement-old-jti',
|
||||
now: NOW + 60_000,
|
||||
}),
|
||||
newAssertion: newOverlap,
|
||||
nowMs: NOW + 120_000,
|
||||
},
|
||||
{
|
||||
async snapshot() {
|
||||
return snapshot(9, ['new-key'], ['old-key'], {
|
||||
serial: 'B2',
|
||||
resourceVersion: 'tls-2',
|
||||
});
|
||||
},
|
||||
async request() {
|
||||
assert.fail('a substituted assertion must fail before HTTP');
|
||||
},
|
||||
},
|
||||
),
|
||||
/append-only old-key revocation/,
|
||||
);
|
||||
|
||||
const revokedSnapshot = snapshot(9, ['new-key'], ['old-key'], {
|
||||
serial: 'B2',
|
||||
resourceVersion: 'tls-2',
|
||||
});
|
||||
const revoked = await revokedPhase(
|
||||
{
|
||||
endpoint: overlap.endpoint,
|
||||
state: overlap,
|
||||
oldAssertion: oldOverlap,
|
||||
newAssertion: newOverlap,
|
||||
nowMs: NOW + 120_000,
|
||||
},
|
||||
{
|
||||
async snapshot() {
|
||||
return revokedSnapshot;
|
||||
},
|
||||
async request(assertionValue, command) {
|
||||
return assertionValue === oldOverlap
|
||||
? failure(revokedSnapshot, 401, 'authentication_required')
|
||||
: success(revokedSnapshot, command.operation, {
|
||||
approval: { version: 2, state: 'approved' },
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.equal(revoked.previousStateSha256, overlap.stateSha256);
|
||||
assert.equal(validateState(revoked, 'revoked'), revoked);
|
||||
assert.deepEqual(revoked.phaseObservedAt, [
|
||||
'2026-07-25T12:00:00.000Z',
|
||||
'2026-07-25T12:01:00.000Z',
|
||||
'2026-07-25T12:02:00.000Z',
|
||||
]);
|
||||
|
||||
const exercise = exerciseFromState(revoked, NOW + 121_000);
|
||||
assert.equal(
|
||||
validateExercise(exercise, NOW + 121_000).fixture,
|
||||
'qinglong/plugin-package-management-live-exercise@v1',
|
||||
);
|
||||
assert.deepEqual(exercise.identity.keysetGenerations, [7, 8, 9]);
|
||||
assert.equal(exercise.rotation.revokedOldStatus, 401);
|
||||
assert.equal(exercise.rotation.readinessSamples.length, 3);
|
||||
assert.throws(
|
||||
() => exerciseFromState(revoked, NOW + 25 * 60 * 60 * 1000),
|
||||
/timeline is stale/,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects tampered state and cannot skip directly to revoked', async () => {
|
||||
const state = {
|
||||
schemaVersion: 1,
|
||||
fixture: 'qinglong/plugin-package-management-live-exercise-state@v1',
|
||||
phase: 'before',
|
||||
recordedAt: new Date(NOW).toISOString(),
|
||||
phaseObservedAt: [new Date(NOW).toISOString()],
|
||||
previousStateSha256: null,
|
||||
clusterIdentitySha256: `sha256:${'1'.repeat(64)}`,
|
||||
endpoint:
|
||||
'https://management.example.com/api/v3/plugin-packages/management',
|
||||
action: {
|
||||
actionRef: 'ql3-live-evidence:123e4567-e89b-42d3-a456-426614174000',
|
||||
approvalRequestId: '123e4567-e89b-42d3-a456-426614174001',
|
||||
proposalAuditEventId: '123e4567-e89b-42d3-a456-426614174002',
|
||||
approvalAuditEventId: '123e4567-e89b-42d3-a456-426614174003',
|
||||
decisionAuditEventId: '123e4567-e89b-42d3-a456-426614174004',
|
||||
decisionId: '123e4567-e89b-42d3-a456-426614174005',
|
||||
approvalVersion: 1,
|
||||
},
|
||||
identity: {
|
||||
issuer: ISSUER,
|
||||
audience: AUDIENCE,
|
||||
requesterSubject: 'tenant/requester',
|
||||
reviewerSubject: 'tenant/reviewer',
|
||||
requesterAssurance: 'multi_factor',
|
||||
reviewerAssurance: 'hardware',
|
||||
oldKid: 'old-key',
|
||||
newKid: null,
|
||||
overlapOldAssertionSha256: null,
|
||||
newAssertionSha256: null,
|
||||
keysetGenerations: [7],
|
||||
},
|
||||
ceremony: {
|
||||
proposalAuditEventId: '123e4567-e89b-42d3-a456-426614174002',
|
||||
approvalAuditEventId: '123e4567-e89b-42d3-a456-426614174003',
|
||||
decisionAuditEventId: '123e4567-e89b-42d3-a456-426614174004',
|
||||
proposeStatus: 200,
|
||||
proposeOperation: 'plugin-package.propose',
|
||||
selfDecisionStatus: 403,
|
||||
selfDecisionError: 'forbidden',
|
||||
reviewerDecisionStatus: 200,
|
||||
reviewerDecisionOperation: 'plugin-package.decide',
|
||||
inspectionStatus: 200,
|
||||
inspectionOperation: 'plugin-package.inspect',
|
||||
},
|
||||
isolation: exactIsolation(),
|
||||
rotation: {
|
||||
overlapOldStatus: null,
|
||||
newStatus: null,
|
||||
revokedOldStatus: null,
|
||||
revokedOldError: null,
|
||||
previousTlsSerial: 'A1',
|
||||
currentTlsSerial: null,
|
||||
previousTlsSecretResourceVersion: 'tls-1',
|
||||
currentTlsSecretResourceVersion: null,
|
||||
readinessSamples: [
|
||||
{
|
||||
phase: 'before',
|
||||
replicas: 2,
|
||||
readyReplicas: 2,
|
||||
unavailableReplicas: 0,
|
||||
tlsProtocol: 'TLSv1.3',
|
||||
},
|
||||
],
|
||||
},
|
||||
stateSha256: '',
|
||||
};
|
||||
state.stateSha256 = stateDigest(state);
|
||||
state.identity.requesterSubject = 'tampered';
|
||||
assert.throws(
|
||||
() => validateState(state, 'before'),
|
||||
PluginPackageManagementLiveExerciseError,
|
||||
);
|
||||
await assert.rejects(
|
||||
revokedPhase(
|
||||
{
|
||||
endpoint: state.endpoint,
|
||||
state,
|
||||
oldAssertion: 'invalid',
|
||||
newAssertion: 'invalid',
|
||||
nowMs: NOW,
|
||||
},
|
||||
{ async snapshot() {} },
|
||||
),
|
||||
PluginPackageManagementLiveExerciseError,
|
||||
);
|
||||
});
|
||||
|
||||
test('probe Pods are tokenless, non-root, bounded and carry only public probe arguments', () => {
|
||||
const pod = probePod(
|
||||
'probe-a',
|
||||
`registry.example/qinglong3@sha256:${'2'.repeat(64)}`,
|
||||
'IfNotPresent',
|
||||
[],
|
||||
{ 'qinglong.io/plugin-package-management-client': 'true' },
|
||||
[
|
||||
'tls',
|
||||
'service.namespace.svc',
|
||||
'8443',
|
||||
'connected',
|
||||
'service.namespace.svc',
|
||||
],
|
||||
);
|
||||
assert.equal(pod.spec.automountServiceAccountToken, false);
|
||||
assert.equal(pod.spec.securityContext.runAsNonRoot, true);
|
||||
assert.deepEqual(pod.spec.containers[0].securityContext.capabilities.drop, [
|
||||
'ALL',
|
||||
]);
|
||||
assert.equal(
|
||||
pod.spec.containers[0].securityContext.readOnlyRootFilesystem,
|
||||
true,
|
||||
);
|
||||
assert.equal(pod.spec.containers[0].resources.limits.memory, '64Mi');
|
||||
assert.doesNotMatch(JSON.stringify(pod), /assertion|authorization|bearer/i);
|
||||
});
|
||||
|
||||
test('cleans only probe Pods created by the current exercise', async () => {
|
||||
const deleted = [];
|
||||
let creates = 0;
|
||||
await assert.rejects(
|
||||
() =>
|
||||
ingressProbes(
|
||||
{
|
||||
run(args) {
|
||||
if (args[0] === 'create') {
|
||||
creates += 1;
|
||||
if (creates === 2) throw new Error('name already exists');
|
||||
return { status: 0, stdout: '', stderr: '' };
|
||||
}
|
||||
if (args.includes('delete')) {
|
||||
deleted.push(args[4]);
|
||||
return { status: 0, stdout: '', stderr: '' };
|
||||
}
|
||||
assert.fail(`unexpected kubectl operation: ${args.join(' ')}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
pods: [{ ip: '10.0.0.10' }],
|
||||
image: `registry.example/qinglong3@sha256:${'2'.repeat(64)}`,
|
||||
imagePullPolicy: 'IfNotPresent',
|
||||
imagePullSecrets: [],
|
||||
},
|
||||
'123e4567-e89b-42d3-a456-426614174000',
|
||||
),
|
||||
/name already exists/,
|
||||
);
|
||||
assert.deepEqual(deleted, ['ql3-management-evidence-123e4567-allowed']);
|
||||
});
|
||||
Reference in New Issue
Block a user