feat(ql3): add cluster run management live gate

This commit is contained in:
whyour
2026-08-12 11:51:14 +08:00
parent 86eb0f1eb3
commit 5c89e3bc60
14 changed files with 2782 additions and 300 deletions
@@ -0,0 +1,87 @@
#!/usr/bin/env node
'use strict';
const assert = require('node:assert/strict');
function imageIdDigest(image) {
assert.match(image.Id, /^sha256:[a-f0-9]{64}$/);
return image.Id;
}
function localManifest(rendered, imageName, localImage) {
const occurrences = rendered.split(imageName).length - 1;
assert.ok(occurrences >= 1, 'reviewed image reference is missing');
return rendered
.replaceAll(imageName, localImage)
.replaceAll('imagePullPolicy: IfNotPresent', 'imagePullPolicy: Never');
}
function applySecret(fixture, name, type, stringData) {
fixture.apply({
apiVersion: 'v1',
kind: 'Secret',
metadata: {
name,
namespace: 'qinglong3-system',
labels: { 'cnpg.io/reload': 'true' },
},
immutable: false,
type,
stringData,
});
}
function psql(fixture, podName, sql) {
return fixture.kubectl(
[
'-n',
'qinglong3-system',
'exec',
podName,
'-c',
'postgres',
'--',
'psql',
'--username=postgres',
'--dbname=qinglong',
'--no-psqlrc',
'--tuples-only',
'--no-align',
'--set=ON_ERROR_STOP=1',
'--command',
sql,
],
{ capture: true, quiet: true },
).stdout;
}
function currentPrimaryPod(fixture) {
const primaryName = fixture.kubectlJson([
'-n',
'qinglong3-system',
'get',
'cluster',
'ql3-postgres',
]).status.currentPrimary;
assert.match(primaryName || '', /^ql3-postgres-[1-9][0-9]*$/);
const pods = fixture.kubectlJson([
'-n',
'qinglong3-system',
'get',
'pods',
'-l',
'cnpg.io/cluster=ql3-postgres',
]).items;
const primary = pods.find((pod) => pod.metadata.name === primaryName);
assert.ok(primary, 'CloudNativePG primary Pod not found');
return primary;
}
module.exports = {
applySecret,
currentPrimaryPod,
imageIdDigest,
localManifest,
psql,
};
+79 -56
View File
@@ -100,9 +100,7 @@ async function waitForTwoPreserved(options) {
'-l',
'app.kubernetes.io/name=' + options.deployment,
])
.items.filter(
(pod) => pod.metadata.deletionTimestamp === undefined,
);
.items.filter((pod) => pod.metadata.deletionTimestamp === undefined);
const ready = pods.filter(podReady);
minimumReady = Math.min(minimumReady, ready.length);
const replacements = ready.filter(
@@ -117,10 +115,7 @@ async function waitForTwoPreserved(options) {
: {
ready: false,
fact:
ready.length +
' ready, ' +
replacements.length +
' replacements',
ready.length + ' ready, ' + replacements.length + ' replacements',
};
});
assert.ok(
@@ -131,6 +126,17 @@ async function waitForTwoPreserved(options) {
}
function createManagementClientExecutor(options) {
const retryableClientCodes = options.retryableClientCodes ?? [
'QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_REQUEST_FAILED',
];
assert.ok(
Array.isArray(retryableClientCodes) &&
retryableClientCodes.length >= 1 &&
retryableClientCodes.every(
(code) => typeof code === 'string' && /^[A-Z0-9_]{1,128}$/.test(code),
),
'management client retryable codes are invalid',
);
options.fixture.apply({
apiVersion: 'v1',
kind: 'ServiceAccount',
@@ -243,9 +249,14 @@ function createManagementClientExecutor(options) {
'--command=/tmp/command.json ' +
'--assertion=/tmp/assertion.jwt 2>&1)"',
' status=$?',
' if [ "$status" -eq 0 ] || { ! printf \'%s\' "$output" | ' +
'grep -q QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_REQUEST_FAILED && ' +
'! printf \'%s\' "$output" | grep -q \'"statusCode":503\'; } || ' +
' if [ "$status" -eq 0 ] || { ' +
retryableClientCodes
.map(
(code) =>
'! printf \'%s\' "$output" | grep -q ' + code,
)
.join(' && ') +
' && ! printf \'%s\' "$output" | grep -q \'"statusCode":503\'; } || ' +
'[ "$attempt" -ge 60 ]; then',
' break',
' fi',
@@ -373,7 +384,10 @@ function createManagementClientExecutor(options) {
assert.equal(output.event, 'command_completed');
assert.equal(output.result.operation, definition.command.operation);
if (expected.resultStatus) {
assert.ok(expected.resultStatus.includes(output.result.status));
const status = expected.resultField
? output.result[expected.resultField]?.status
: output.result.status;
assert.ok(expected.resultStatus.includes(status));
}
} else {
assert.equal(
@@ -409,6 +423,36 @@ function createManagementClientExecutor(options) {
};
}
function managementHealthStatus(options) {
const script = [
"const fs=require('node:fs');const https=require('node:https');",
"const request=https.request({host:'127.0.0.1',port:Number(process.argv[1]),path:process.argv[2],",
'servername:process.argv[3],ca:fs.readFileSync(process.argv[4]),',
"minVersion:'TLSv1.3',maxVersion:'TLSv1.3',rejectUnauthorized:true,agent:false},",
"(response)=>{response.resume();response.on('end',()=>process.stdout.write(String(response.statusCode)))});",
"request.on('error',(error)=>{process.stderr.write(error.message);process.exitCode=1});request.end();",
].join('\n');
return Number(
options.fixture.kubectl(
[
'-n',
options.namespace,
'exec',
options.podName,
'--',
'node',
'-e',
script,
String(options.port),
options.route,
options.servername,
options.caFile,
],
{ capture: true, quiet: true },
).stdout,
);
}
function podTcpProbe(options) {
const script = [
"const net=require('node:net');let finished=false;",
@@ -437,9 +481,7 @@ function podTcpProbe(options) {
async function clientTcpProbe(options) {
const labels = {
'app.kubernetes.io/name': options.appName,
...(options.labelled
? { [options.networkPolicyLabel]: 'true' }
: {}),
...(options.labelled ? { [options.networkPolicyLabel]: 'true' } : {}),
};
const script = [
"const fs=require('node:fs');const net=require('node:net');let finished=false;let attempt=0;let socket;",
@@ -497,30 +539,25 @@ async function clientTcpProbe(options) {
},
},
});
const observed = await waitFor(
options.name + ' completion',
180_000,
() => {
const job = options.fixture.kubectlJson([
'-n',
options.namespace,
'get',
'job',
options.name,
]);
const complete = job.status.conditions?.some(
(condition) =>
condition.type === 'Complete' && condition.status === 'True',
);
const failed = job.status.conditions?.some(
(condition) =>
condition.type === 'Failed' && condition.status === 'True',
);
return complete || failed
? { ready: true, value: { complete, failed } }
: { ready: false, fact: JSON.stringify(job.status ?? {}) };
},
);
const observed = await waitFor(options.name + ' completion', 180_000, () => {
const job = options.fixture.kubectlJson([
'-n',
options.namespace,
'get',
'job',
options.name,
]);
const complete = job.status.conditions?.some(
(condition) =>
condition.type === 'Complete' && condition.status === 'True',
);
const failed = job.status.conditions?.some(
(condition) => condition.type === 'Failed' && condition.status === 'True',
);
return complete || failed
? { ready: true, value: { complete, failed } }
: { ready: false, fact: JSON.stringify(job.status ?? {}) };
});
const probePod = (
await waitFor(options.name + ' terminal pod', 30_000, () => {
const pods = options.fixture.kubectlJson([
@@ -541,16 +578,8 @@ async function clientTcpProbe(options) {
const terminated = probePod.status.containerStatuses[0].state.terminated;
const observation =
options.name + ': ' + (terminated.message ?? 'no-message');
assert.equal(
observed.value.complete,
options.expectedConnected,
observation,
);
assert.equal(
observed.value.failed,
!options.expectedConnected,
observation,
);
assert.equal(observed.value.complete, options.expectedConnected, observation);
assert.equal(observed.value.failed, !options.expectedConnected, observation);
assert.equal(
terminated.exitCode === 0,
options.expectedConnected,
@@ -562,14 +591,7 @@ async function clientTcpProbe(options) {
assert.match(terminated.message ?? '', /^denied:/);
}
options.fixture.kubectl(
[
'-n',
options.namespace,
'delete',
'job',
options.name,
'--wait=false',
],
['-n', options.namespace, 'delete', 'job', options.name, '--wait=false'],
{ capture: true, quiet: true },
);
return options.expectedConnected
@@ -580,6 +602,7 @@ async function clientTcpProbe(options) {
module.exports = {
clientTcpProbe,
createManagementClientExecutor,
managementHealthStatus,
patchManagementGeneration,
podReady,
podTcpProbe,
+46 -2
View File
@@ -57,7 +57,8 @@ function createManagementIdentityCeremony(options) {
});
}
function assertion(key, suffix = crypto.randomUUID()) {
function assertionForSubject(key, subject, suffix = crypto.randomUUID()) {
assert.match(subject, /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
const now = Math.floor(Date.now() / 1_000);
const header = Buffer.from(
JSON.stringify({
@@ -77,6 +78,43 @@ function createManagementIdentityCeremony(options) {
iss: options.issuer,
jti: options.jtiPrefix + '-' + suffix,
ql3_purpose: options.purpose,
sub: subject,
}),
).toString('base64url');
const signed = header + '.' + payload;
return (
signed +
'.' +
crypto
.sign(null, Buffer.from(signed, 'ascii'), key.privateKey)
.toString('base64url')
);
}
function assertion(key, suffix = crypto.randomUUID()) {
return assertionForSubject(key, options.subject, suffix);
}
function weakAssertion(key, suffix = crypto.randomUUID()) {
const now = Math.floor(Date.now() / 1_000);
const header = Buffer.from(
JSON.stringify({
alg: 'EdDSA',
kid: key.kid,
typ: options.tokenType,
}),
).toString('base64url');
const payload = Buffer.from(
JSON.stringify({
acr: 'urn:ql3:password',
amr: ['pwd'],
aud: options.audience,
auth_time: now - 1,
exp: now + 290,
iat: now,
iss: options.issuer,
jti: options.jtiPrefix + '-weak-' + suffix,
ql3_purpose: options.purpose,
sub: options.subject,
}),
).toString('base64url');
@@ -90,7 +128,13 @@ function createManagementIdentityCeremony(options) {
);
}
return Object.freeze({ assertion, keyset, reviewedKey });
return Object.freeze({
assertion,
assertionForSubject,
keyset,
reviewedKey,
weakAssertion,
});
}
module.exports = { createManagementIdentityCeremony };
@@ -0,0 +1,194 @@
#!/usr/bin/env node
'use strict';
const assert = require('node:assert/strict');
const crypto = require('node:crypto');
function eventId(ordinal) {
assert.ok(Number.isSafeInteger(ordinal) && ordinal >= 1 && ordinal < 1e12);
return '41000000-0000-4000-8000-' + String(ordinal).padStart(12, '0');
}
function retryCommand(projectId, sourceRunId, requestId, mutationId, ordinal) {
return Object.freeze({
schemaVersion: 1,
operation: 'run.retry',
request: Object.freeze({
projectId,
sourceRunId,
requestId,
auditEventId: eventId(ordinal),
failureAuditEventId: eventId(ordinal + 500_000),
body: Object.freeze({
schema: 'qinglong/run-manual-retry@v1',
mutationId,
expectedRunVersion: 3,
expectedRunStatus: 'failed',
}),
}),
});
}
function stopCommand(projectId, runId, requestId, mutationId, ordinal) {
return Object.freeze({
schemaVersion: 1,
operation: 'run.stop',
request: Object.freeze({
projectId,
runId,
requestId,
auditEventId: eventId(ordinal),
failureAuditEventId: eventId(ordinal + 500_000),
body: Object.freeze({
schema: 'qinglong/run-cancellation@v1',
mutationId,
}),
}),
});
}
function sqlString(value) {
assert.equal(typeof value, 'string');
return "'" + value.replaceAll("'", "''") + "'";
}
function seedRunManagement(fixture, podName, values, psql) {
const nowMs = Date.now();
const sourceDigest = 'a'.repeat(64);
const taskRevision = `qltd:v1:1:${sourceDigest}`;
const taskDigest = 'b'.repeat(64);
const planDigest = 'c'.repeat(64);
psql(
fixture,
podName,
[
'BEGIN;',
'INSERT INTO "ql3"."projects" (id, name, slug, status, version, created_at_ms, updated_at_ms)',
`VALUES (${sqlString(
values.projectId,
)}, 'Run Management Live', ${sqlString(
values.projectId,
)}, 'active', 1, ${nowMs}, ${nowMs});`,
'INSERT INTO "ql3"."project_role_bindings" (project_id, subject_type, subject_id, version, state, role, mutation_id, changed_by_type, changed_by_id, created_at_ms)',
`VALUES (${sqlString(values.projectId)}, 'user', ${sqlString(
values.operatorId,
)}, 1, 'active', 'operator', ${sqlString(
'binding-' + values.suffix,
)}, 'system', 'run-management-live', ${nowMs});`,
'INSERT INTO "ql3"."task_definitions" (project_id, task_id, current_revision, created_at_ms, updated_at_ms)',
`VALUES (${sqlString(values.projectId)}, ${sqlString(
values.taskId,
)}, 1, ${nowMs}, ${nowMs});`,
'INSERT INTO "ql3"."task_definition_revisions" (project_id, task_id, revision, mutation_id, name, kind, spec_json, labels_json, enabled, content_digest, created_at_ms)',
`VALUES (${sqlString(values.projectId)}, ${sqlString(
values.taskId,
)}, 1, ${sqlString(
crypto.randomUUID(),
)}::uuid, 'Run Management Live Task', 'command', '{"schema":"qinglong/command@v1","config":{"command":{"kind":"argv","file":"/bin/echo","args":["run-management-live"]}}}'::jsonb, '{}'::jsonb, true, ${sqlString(
taskDigest,
)}, ${nowMs});`,
'INSERT INTO "ql3"."task_execution_revisions" (project_id, task_id, source_revision, task_revision, source_content_digest, executor_type, plan_schema, plan_json, content_digest, created_at_ms)',
`VALUES (${sqlString(values.projectId)}, ${sqlString(
values.taskId,
)}, 1, ${sqlString(taskRevision)}, ${sqlString(
sourceDigest,
)}, 'remote_worker', 'qinglong/command-execution@v1', '{"file":"/bin/echo","args":["run-management-live"]}'::jsonb, ${sqlString(
planDigest,
)}, ${nowMs});`,
'INSERT INTO "ql3"."runs" (id, project_id, task_id, task_revision, task_name, task_snapshot_ref, trigger_type, execution_origin, execution_owner, status, version, event_sequence, priority, created_at_ms, queued_at_ms, finished_at_ms, error_code, error_summary)',
`VALUES (${sqlString(values.sourceRunId)}, ${sqlString(
values.projectId,
)}, ${sqlString(values.taskId)}, ${sqlString(
taskRevision,
)}, 'Run Management Live Task', ${sqlString(
taskRevision,
)}, 'manual', 'manual', 'runtime', 'failed', 3, 3, 0, ${nowMs}, ${nowMs}, ${nowMs}, 'LIVE_SOURCE_FAILURE', 'terminal source for Run management live');`,
'INSERT INTO "ql3"."run_attempts" (id, run_id, attempt, status, executor_type, callback_sequence, created_at_ms, finished_at_ms, error_code, error_summary)',
`VALUES (${sqlString(values.sourceAttemptId)}, ${sqlString(
values.sourceRunId,
)}, 1, 'failed', 'remote_worker', 0, ${nowMs}, ${nowMs}, 'LIVE_SOURCE_FAILURE', 'terminal source for Run management live');`,
'INSERT INTO "ql3"."run_events" (id, run_id, sequence, type, dedupe_key, actor_type, actor_id, attempt_id, payload, created_at_ms)',
`VALUES (${sqlString(crypto.randomUUID())}, ${sqlString(
values.sourceRunId,
)}, 1, 'run.created', ${sqlString(
'run-management-live-created-' + values.suffix,
)}, 'user', ${sqlString(values.operatorId)}, ${sqlString(
values.sourceAttemptId,
)}, '{"status":"created","version":1}'::jsonb, ${nowMs}),`,
`(${sqlString(crypto.randomUUID())}, ${sqlString(
values.sourceRunId,
)}, 2, 'run.queued', ${sqlString(
'run-management-live-queued-' + values.suffix,
)}, 'user', ${sqlString(values.operatorId)}, ${sqlString(
values.sourceAttemptId,
)}, '{"from_status":"created","to_status":"queued","version":2}'::jsonb, ${nowMs}),`,
`(${sqlString(crypto.randomUUID())}, ${sqlString(
values.sourceRunId,
)}, 3, 'run.failed', ${sqlString(
'run-management-live-failed-' + values.suffix,
)}, 'executor', 'run-management-live', ${sqlString(
values.sourceAttemptId,
)}, '{"from_status":"queued","to_status":"failed","version":3,"error_code":"LIVE_SOURCE_FAILURE"}'::jsonb, ${nowMs});`,
'COMMIT;',
].join('\n'),
);
return Object.freeze({ taskRevision, taskDigest, planDigest });
}
function durableRunManagementFacts(fixture, podName, values, psql) {
return JSON.parse(
psql(
fixture,
podName,
[
'SELECT json_build_object(',
' \'sourceRunStatus\', (SELECT status FROM "ql3"."runs" WHERE id = ' +
sqlString(values.sourceRunId) +
'),',
' \'retryRunCount\', (SELECT count(*)::integer FROM "ql3"."runs" WHERE project_id = ' +
sqlString(values.projectId) +
" AND trigger_type = 'run_manual_retry'),",
' \'retryAttemptCount\', (SELECT count(*)::integer FROM "ql3"."run_attempts" AS attempt JOIN "ql3"."runs" AS run ON run.id = attempt.run_id WHERE run.project_id = ' +
sqlString(values.projectId) +
" AND run.trigger_type = 'run_manual_retry'),",
' \'retryEventCount\', (SELECT count(*)::integer FROM "ql3"."run_events" AS event JOIN "ql3"."runs" AS run ON run.id = event.run_id WHERE run.project_id = ' +
sqlString(values.projectId) +
" AND run.trigger_type = 'run_manual_retry' AND event.type IN ('run.created', 'run.queued')),",
' \'stoppedRunCount\', (SELECT count(*)::integer FROM "ql3"."runs" WHERE project_id = ' +
sqlString(values.projectId) +
" AND trigger_type = 'run_manual_retry' AND cancel_requested_at_ms IS NOT NULL AND cancel_reason = 'user'),",
' \'stopEventCount\', (SELECT count(*)::integer FROM "ql3"."run_events" AS event JOIN "ql3"."runs" AS run ON run.id = event.run_id WHERE run.project_id = ' +
sqlString(values.projectId) +
" AND event.type = 'run.cancel_requested'),",
' \'allowedAuditCount\', (SELECT count(*)::integer FROM "ql3"."security_audit_events" WHERE project_id = ' +
sqlString(values.projectId) +
" AND operation_id IN ('run.retry', 'run.stop') AND outcome = 'allowed'),",
' \'deniedAuditCount\', (SELECT count(*)::integer FROM "ql3"."security_audit_events" WHERE project_id = ' +
sqlString(values.projectId) +
" AND operation_id IN ('run.retry', 'run.stop') AND outcome = 'denied'),",
' \'weakAuthenticationAuditCount\', (SELECT count(*)::integer FROM "ql3"."security_audit_events" WHERE project_id = ' +
sqlString(values.projectId) +
" AND request_id = 'run-live-weak'),",
' \'duplicateMutationCount\', greatest(0, (SELECT count(*)::integer FROM "ql3"."runs" WHERE project_id = ' +
sqlString(values.projectId) +
' AND trigger_type = \'run_manual_retry\') - 1) + greatest(0, (SELECT count(*)::integer FROM "ql3"."run_events" AS event JOIN "ql3"."runs" AS run ON run.id = event.run_id WHERE run.project_id = ' +
sqlString(values.projectId) +
" AND event.type = 'run.cancel_requested') - 1),",
' \'identityGeneration\', (SELECT generation::integer FROM "ql3"."plugin_package_identity_keyset_ledger" WHERE authority = \'run-management\'),',
' \'migrationCount\', (SELECT count(*)::integer FROM "ql3"."schema_migrations"),',
' \'controlCoreCapability\', (SELECT contract_version::integer FROM "ql3"."schema_capabilities" WHERE contract_name = \'control-core\'),',
" 'postgresVersionNumber', current_setting('server_version_num')::integer)",
].join('\n'),
),
);
}
module.exports = {
durableRunManagementFacts,
eventId,
retryCommand,
seedRunManagement,
sqlString,
stopCommand,
};
@@ -16,6 +16,7 @@ const { createMutualTlsPki } = require('./lib/ql3-live-pki.cjs');
const {
clientTcpProbe,
createManagementClientExecutor,
managementHealthStatus,
patchManagementGeneration,
podReady,
podTcpProbe,
@@ -90,10 +91,7 @@ const identity = createManagementIdentityCeremony({
});
function sha256(value) {
return (
'sha256:' +
crypto.createHash('sha256').update(value).digest('hex')
);
return 'sha256:' + crypto.createHash('sha256').update(value).digest('hex');
}
function randomSecret() {
@@ -102,9 +100,7 @@ function randomSecret() {
function eventId(ordinal) {
assert.ok(Number.isSafeInteger(ordinal) && ordinal >= 1 && ordinal < 1e12);
return (
'40000000-0000-4000-8000-' + String(ordinal).padStart(12, '0')
);
return '40000000-0000-4000-8000-' + String(ordinal).padStart(12, '0');
}
function reviewedKey(kid) {
@@ -119,75 +115,12 @@ function assertion(key, suffix) {
return identity.assertion(key, suffix);
}
function assertionForSubject(
key,
subject,
suffix = crypto.randomUUID(),
) {
assert.match(subject, /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
const now = Math.floor(Date.now() / 1_000);
const header = Buffer.from(
JSON.stringify({
alg: 'EdDSA',
kid: key.kid,
typ: 'ql3-approval-management+jwt',
}),
).toString('base64url');
const payload = Buffer.from(
JSON.stringify({
acr: 'urn:ql3:mfa',
amr: ['pwd', 'otp'],
aud: AUDIENCE,
auth_time: now - 1,
exp: now + 290,
iat: now,
iss: ISSUER,
jti: 'ql3-approval-live-subject-' + suffix,
ql3_purpose: 'approval-management',
sub: subject,
}),
).toString('base64url');
const signed = header + '.' + payload;
return (
signed +
'.' +
crypto
.sign(null, Buffer.from(signed, 'ascii'), key.privateKey)
.toString('base64url')
);
function assertionForSubject(key, subject, suffix = crypto.randomUUID()) {
return identity.assertionForSubject(key, subject, 'subject-' + suffix);
}
function weakAssertion(key, suffix = crypto.randomUUID()) {
const now = Math.floor(Date.now() / 1_000);
const header = Buffer.from(
JSON.stringify({
alg: 'EdDSA',
kid: key.kid,
typ: 'ql3-approval-management+jwt',
}),
).toString('base64url');
const payload = Buffer.from(
JSON.stringify({
acr: 'urn:ql3:password',
amr: ['pwd'],
aud: AUDIENCE,
auth_time: now - 1,
exp: now + 290,
iat: now,
iss: ISSUER,
jti: 'ql3-approval-live-weak-' + suffix,
ql3_purpose: 'approval-management',
sub: 'approval-operator',
}),
).toString('base64url');
const signed = header + '.' + payload;
return (
signed +
'.' +
crypto
.sign(null, Buffer.from(signed, 'ascii'), key.privateKey)
.toString('base64url')
);
return identity.weakAssertion(key, suffix);
}
function commandBase(projectId, approvalRequestId, requestId, ordinal) {
@@ -204,12 +137,7 @@ function inspectCommand(projectId, approvalRequestId, requestId, ordinal) {
return Object.freeze({
schemaVersion: 1,
operation: 'approval.inspect',
request: commandBase(
projectId,
approvalRequestId,
requestId,
ordinal,
),
request: commandBase(projectId, approvalRequestId, requestId, ordinal),
});
}
@@ -224,12 +152,7 @@ function decisionCommand(
schemaVersion: 1,
operation: 'approval.decide',
request: Object.freeze({
...commandBase(
projectId,
approvalRequestId,
requestId,
ordinal,
),
...commandBase(projectId, approvalRequestId, requestId, ordinal),
expectedVersion: 1,
expectedAction: ACTION,
decisionId,
@@ -327,12 +250,7 @@ function loadApprovalContract() {
return require(file);
}
function seedApproval(
fixture,
primaryPod,
projectId,
approvalRequestId,
) {
function seedApproval(fixture, primaryPod, projectId, approvalRequestId) {
const { approvalRequestDigest, createApprovalRequest } =
loadApprovalContract();
const requestedAtMs = Date.now() - 1_000;
@@ -422,31 +340,15 @@ function patchGeneration(fixture, generation, annotations = {}) {
}
function healthStatus(fixture, pod, route) {
const script = [
"const fs=require('node:fs');const https=require('node:https');",
"const request=https.request({host:'127.0.0.1',port:8447,path:process.argv[1],",
"servername:process.argv[2],ca:fs.readFileSync('/var/run/secrets/qinglong3/approval-management-tls/ca.crt'),",
"minVersion:'TLSv1.3',maxVersion:'TLSv1.3',rejectUnauthorized:true,agent:false},",
"(response)=>{response.resume();response.on('end',()=>process.stdout.write(String(response.statusCode)))});",
"request.on('error',(error)=>{process.stderr.write(error.message);process.exitCode=1});request.end();",
].join('\n');
return Number(
fixture.kubectl(
[
'-n',
NAMESPACE,
'exec',
pod.metadata.name,
'--',
'node',
'-e',
script,
route,
SERVERNAME,
],
{ capture: true, quiet: true },
).stdout,
);
return managementHealthStatus({
fixture,
namespace: NAMESPACE,
podName: pod.metadata.name,
port: 8447,
route,
servername: SERVERNAME,
caFile: '/var/run/secrets/qinglong3/approval-management-tls/ca.crt',
});
}
function privateReportPath(argv) {
@@ -507,10 +409,7 @@ async function main(argv = process.argv.slice(2)) {
);
const preloadTag = imageTag(reviewedImage);
run(fixture.docker, ['tag', reviewedImage, preloadTag]);
fixture.loadImage(
preloadTag,
path.basename(preloadTag) + '.tar',
);
fixture.loadImage(preloadTag, path.basename(preloadTag) + '.tar');
}
const sourceRevision = run('git', ['rev-parse', 'HEAD'], {
@@ -615,28 +514,24 @@ async function main(argv = process.argv.slice(2)) {
'--timeout=20m',
]);
const databasePods = (
await waitFor(
'three ready CloudNativePG instances',
600_000,
() => {
const pods = fixture
.kubectlJson([
'-n',
NAMESPACE,
'get',
'pods',
'-l',
'cnpg.io/cluster=' + POSTGRES_CLUSTER,
])
.items.filter(podReady);
return pods.length === 3
? { ready: true, value: pods }
: {
ready: false,
fact: pods.length + '/3 ready database Pods',
};
},
)
await waitFor('three ready CloudNativePG instances', 600_000, () => {
const pods = fixture
.kubectlJson([
'-n',
NAMESPACE,
'get',
'pods',
'-l',
'cnpg.io/cluster=' + POSTGRES_CLUSTER,
])
.items.filter(podReady);
return pods.length === 3
? { ready: true, value: pods }
: {
ready: false,
fact: pods.length + '/3 ready database Pods',
};
})
).value;
const migrationManifest = localManifest(
@@ -685,12 +580,7 @@ async function main(argv = process.argv.slice(2)) {
const approvalRequestId = 'approval-request-' + suffix;
const decisionId = 'approval-decision-' + suffix;
const primary = currentPrimaryPod(fixture);
seedApproval(
fixture,
primary,
projectId,
approvalRequestId,
);
seedApproval(fixture, primary, projectId, approvalRequestId);
const pki = createMutualTlsPki({
directory: fixture.temporary,
@@ -764,13 +654,8 @@ async function main(argv = process.argv.slice(2)) {
1,
);
assert.equal(
fixture.kubectlJson([
'-n',
NAMESPACE,
'get',
'pdb',
DEPLOYMENT,
]).spec.minAvailable,
fixture.kubectlJson(['-n', NAMESPACE, 'get', 'pdb', DEPLOYMENT]).spec
.minAvailable,
1,
);
for (const pod of managerPods) {
@@ -876,9 +761,7 @@ async function main(argv = process.argv.slice(2)) {
{ statusCode: 403, responseCode: 'forbidden' },
);
const generation1Uids = new Set(
managerPods.map((pod) => pod.metadata.uid),
);
const generation1Uids = new Set(managerPods.map((pod) => pod.metadata.uid));
applyIdentity(keysets[1]);
patchGeneration(fixture, 2);
const generation2 = await waitForTwoPreserved({
@@ -925,9 +808,7 @@ async function main(argv = process.argv.slice(2)) {
assert.equal(decided.output.result.approval.state, 'approved');
assert.equal(decided.output.result.approval.version, 2);
const generation2Uids = new Set(
managerPods.map((pod) => pod.metadata.uid),
);
const generation2Uids = new Set(managerPods.map((pod) => pod.metadata.uid));
applyIdentity(keysets[2]);
patchGeneration(fixture, 3);
const generation3 = await waitForTwoPreserved({
@@ -987,9 +868,7 @@ async function main(argv = process.argv.slice(2)) {
'-l',
'app.kubernetes.io/name=' + DEPLOYMENT,
])
.items.filter(
(pod) => pod.metadata.deletionTimestamp === undefined,
);
.items.filter((pod) => pod.metadata.deletionTimestamp === undefined);
const ready = pods.filter(podReady);
const candidate = pods.find(
(pod) =>
@@ -1127,27 +1006,23 @@ async function main(argv = process.argv.slice(2)) {
};
},
);
await waitFor(
'CloudNativePG recovery to three instances',
900_000,
() => {
const status = fixture.kubectlJson([
'-n',
NAMESPACE,
'get',
'cluster',
POSTGRES_CLUSTER,
]).status;
return Number(status.readyInstances) === 3
? { ready: true, value: status }
: {
ready: false,
fact:
String(status.readyInstances ?? 0) +
'/3 ready database instances',
};
},
);
await waitFor('CloudNativePG recovery to three instances', 900_000, () => {
const status = fixture.kubectlJson([
'-n',
NAMESPACE,
'get',
'cluster',
POSTGRES_CLUSTER,
]).status;
return Number(status.readyInstances) === 3
? { ready: true, value: status }
: {
ready: false,
fact:
String(status.readyInstances ?? 0) +
'/3 ready database instances',
};
});
const databaseService = fixture.kubectlJson([
'-n',
@@ -1175,8 +1050,7 @@ async function main(argv = process.argv.slice(2)) {
managerPods.map((pod, index) =>
executeClient(
{
name:
'ql3-approval-database-unavailable-' + String(index + 1),
name: 'ql3-approval-database-unavailable-' + String(index + 1),
target: pod,
command: decisionCommand(
projectId,
@@ -1209,9 +1083,7 @@ async function main(argv = process.argv.slice(2)) {
? { ready: true, value: current }
: {
ready: false,
fact:
String(current.status.readyReplicas ?? 0) +
' ready replicas',
fact: String(current.status.readyReplicas ?? 0) + ' ready replicas',
};
});
assert.deepEqual(
@@ -1238,35 +1110,29 @@ async function main(argv = process.argv.slice(2)) {
},
]),
]);
await waitFor(
'restored CloudNativePG service endpoint',
120_000,
() => {
const endpoints = fixture.kubectlJson([
'-n',
NAMESPACE,
'get',
'endpoints',
POSTGRES_CLUSTER + '-rw',
]);
const count = endpoints.subsets?.flatMap(
(subset) => subset.addresses ?? [],
).length;
return count >= 1
? { ready: true, value: count }
: {
ready: false,
fact: String(count ?? 0) + ' service endpoints',
};
},
);
await waitFor('restored CloudNativePG service endpoint', 120_000, () => {
const endpoints = fixture.kubectlJson([
'-n',
NAMESPACE,
'get',
'endpoints',
POSTGRES_CLUSTER + '-rw',
]);
const count = endpoints.subsets?.flatMap(
(subset) => subset.addresses ?? [],
).length;
return count >= 1
? { ready: true, value: count }
: {
ready: false,
fact: String(count ?? 0) + ' service endpoints',
};
});
assert.deepEqual(
managerPods.map((pod) => healthStatus(fixture, pod, '/readyz')),
[503, 503],
);
const staleUids = new Set(
managerPods.map((pod) => pod.metadata.uid),
);
const staleUids = new Set(managerPods.map((pod) => pod.metadata.uid));
patchGeneration(fixture, '3-database-recovered');
managerPods = await readyManagementPods({
...managerOptions(fixture),
@@ -1277,8 +1143,7 @@ async function main(argv = process.argv.slice(2)) {
managerPods.map((pod, index) =>
executeClient(
{
name:
'ql3-approval-database-recovered-' + String(index + 1),
name: 'ql3-approval-database-recovered-' + String(index + 1),
target: pod,
command: decisionCommand(
projectId,
@@ -1402,10 +1267,7 @@ async function main(argv = process.argv.slice(2)) {
resource,
'-n',
NAMESPACE,
'--as=system:serviceaccount:' +
NAMESPACE +
':' +
DEPLOYMENT,
'--as=system:serviceaccount:' + NAMESPACE + ':' + DEPLOYMENT,
],
{ capture: true, quiet: true, allowFailure: true },
);
@@ -1576,12 +1438,8 @@ async function main(argv = process.argv.slice(2)) {
port: 8447,
replicas: deployment.spec.replicas,
readyReplicas: managerPods.length,
podIdentitySha256: managerPods.map((pod) =>
sha256(pod.metadata.uid),
),
nodeIdentitySha256: managerPods.map((pod) =>
sha256(pod.spec.nodeName),
),
podIdentitySha256: managerPods.map((pod) => sha256(pod.metadata.uid)),
nodeIdentitySha256: managerPods.map((pod) => sha256(pod.spec.nodeName)),
serviceAccount: DEPLOYMENT,
automountServiceAccountToken: false,
requiredPodAntiAffinity: true,
@@ -1620,8 +1478,7 @@ async function main(argv = process.argv.slice(2)) {
activeNewAssertionAccepted: replayed.statusCode === 200,
rollbackSurgeFailedClosed: Boolean(rollback.value),
twoReadyReplicasPreserved:
generation2.minimumReady >= 2 &&
generation3.minimumReady >= 2,
generation2.minimumReady >= 2 && generation3.minimumReady >= 2,
durableGenerationReachedThree: durable.identityGeneration === 3,
},
certificateRotation: {
@@ -1630,16 +1487,13 @@ async function main(argv = process.argv.slice(2)) {
previousBundleSha256,
currentBundleSha256,
oldClientAcceptedBefore: initialRequests[0].statusCode === 200,
replacementClientAcceptedBefore:
initialRequests[1].statusCode === 200,
replacementClientAcceptedBefore: initialRequests[1].statusCode === 200,
oldClientRejectedAfter: revokedCertificate.statusCode === 401,
replacementClientAcceptedAfter:
activeCertificate.statusCode === 200,
replacementClientAcceptedAfter: activeCertificate.statusCode === 200,
fullPodReplacement: managerPods.every(
(pod) => !preCertificateUids.has(pod.metadata.uid),
),
allReplicasReadyThroughout:
certificateRollout.minimumReady >= 2,
allReplicasReadyThroughout: certificateRollout.minimumReady >= 2,
},
availability: {
databaseFailureWithdrewReadiness: true,
@@ -1660,8 +1514,7 @@ async function main(argv = process.argv.slice(2)) {
publicInternetEgressDenied,
cloudNativePgEgressAllowed,
managerSecretReadDenied: canI('get', 'secrets') === 'no',
managerMutationRbacDenied:
canI('patch', 'deployments.apps') === 'no',
managerMutationRbacDenied: canI('patch', 'deployments.apps') === 'no',
},
durability: {
approvalVersion: durable.approvalVersion,
@@ -1686,15 +1539,13 @@ async function main(argv = process.argv.slice(2)) {
twoManagerPodsOnDistinctNodes:
new Set(managerPods.map((pod) => pod.spec.nodeName)).size === 2,
tls13ProductClientAcrossBothPods:
new Set(baselineSuccesses.map((entry) => entry.targetPod)).size >=
2,
new Set(baselineSuccesses.map((entry) => entry.targetPod)).size >= 2,
strongUserDecision:
weakUserRejected.statusCode === 401 &&
outsiderDenied.statusCode === 403 &&
decided.statusCode === 200,
identityProjectionRotation: durable.identityGeneration === 3,
certificateRevocationRollout:
revokedCertificate.statusCode === 401,
certificateRevocationRollout: revokedCertificate.statusCode === 401,
databaseReadinessFence: true,
durableFactsSurvivedFailover: true,
leastPrivilege: rolesLeastPrivilege,
@@ -1739,7 +1590,9 @@ if (require.main === module) {
main().catch((error) => {
process.stderr.write(
'QL3 approval management Kubernetes live contract failed: ' +
(error instanceof Error ? error.stack || error.message : String(error)) +
(error instanceof Error
? error.stack || error.message
: String(error)) +
'\n',
);
process.exitCode = 1;
@@ -0,0 +1,488 @@
#!/usr/bin/env node
'use strict';
const fs = require('node:fs');
const path = require('node:path');
const FIXTURE = 'qinglong/run-management-kubernetes-live-contract@v1';
const LIMITATIONS = Object.freeze([
'three privileged K3s Docker nodes are not production infrastructure or control-plane HA evidence',
'identity assertions use a deterministic local strong-User ceremony rather than an external IdP',
'CloudNativePG failover inside one Docker host is not infrastructure STONITH evidence',
]);
const BANNED_KEYS = new Set([
'assertion',
'authorization',
'bearer',
'certificate',
'clientkey',
'connectionstring',
'dsn',
'kubeconfig',
'password',
'privatekey',
'secret',
'tlskey',
'token',
]);
function exactKeys(value, expected) {
return (
value !== null &&
typeof value === 'object' &&
!Array.isArray(value) &&
JSON.stringify(Object.keys(value).sort()) ===
JSON.stringify([...expected].sort())
);
}
function isDigest(value) {
return typeof value === 'string' && /^sha256:[a-f0-9]{64}$/.test(value);
}
function isIsoTime(value) {
return (
typeof value === 'string' &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/.test(value) &&
Number.isFinite(Date.parse(value))
);
}
function isToken(value, maximum = 128) {
return (
typeof value === 'string' &&
value.length >= 1 &&
value.length <= maximum &&
/^[A-Za-z0-9][A-Za-z0-9._:/@+-]*$/.test(value)
);
}
function containsSensitiveMaterial(value, key = '') {
if (BANNED_KEYS.has(key.toLowerCase())) return true;
if (typeof value === 'string') {
return (
/-----BEGIN (?:CERTIFICATE|(?:RSA |EC |OPENSSH )?PRIVATE KEY)-----/.test(
value,
) ||
/postgres(?:ql)?:\/\/[^/\s]+:[^@\s]+@/i.test(value) ||
/\beyJ[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/.test(
value,
)
);
}
if (Array.isArray(value)) {
return value.some((entry) => containsSensitiveMaterial(entry));
}
if (value && typeof value === 'object') {
return Object.entries(value).some(([childKey, child]) =>
containsSensitiveMaterial(child, childKey),
);
}
return false;
}
function validKubernetesVersion(value) {
const match =
typeof value === 'string'
? /^v1\.([0-9]{2,3})\.([0-9]+)(?:[-+][0-9A-Za-z](?:[0-9A-Za-z.-]{0,62}[0-9A-Za-z])?)?$/.exec(
value,
)
: null;
return Boolean(match && Number(match[1]) >= 32);
}
function uniqueDigests(value, count) {
return (
Array.isArray(value) &&
value.length === count &&
value.every(isDigest) &&
new Set(value).size === count
);
}
function allTrue(value, expected) {
return (
exactKeys(value, expected) && expected.every((key) => value[key] === true)
);
}
function validateRunManagementKubernetesLiveReport(report) {
const findings = [];
const reject = (code, detail) =>
findings.push(Object.freeze({ code, detail }));
if (
!exactKeys(report, [
'schemaVersion',
'fixture',
'observedAt',
'platform',
'database',
'deployment',
'client',
'identityRotation',
'certificateRotation',
'availability',
'isolation',
'durability',
'gates',
'limitations',
]) ||
report?.schemaVersion !== 1 ||
report?.fixture !== FIXTURE ||
!isIsoTime(report?.observedAt)
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_REPORT_SHAPE',
'the report must use the exact versioned Run management live envelope',
);
}
if (containsSensitiveMaterial(report)) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_SECRET_EXPOSURE',
'the report must not contain credentials, assertions, certificates, DSNs, kubeconfig or private keys',
);
}
const platform = report?.platform;
if (
!exactKeys(platform, [
'distribution',
'kubernetesVersion',
'architecture',
'kubernetesImageId',
'managementImageId',
'cniName',
'cniDistributionBinding',
'controlPlaneNodes',
'workerNodes',
'cniReadyNodes',
]) ||
platform?.distribution !== 'k3s' ||
!validKubernetesVersion(platform?.kubernetesVersion) ||
!['amd64', 'arm64'].includes(platform?.architecture) ||
!isDigest(platform?.kubernetesImageId) ||
!isDigest(platform?.managementImageId) ||
platform?.cniName !== 'flannel' ||
platform?.cniDistributionBinding !== 'rancher/k3s:v1.34.3-k3s1' ||
platform?.controlPlaneNodes !== 1 ||
platform?.workerNodes !== 2 ||
platform?.cniReadyNodes !== 3
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_PLATFORM',
'the fixture must bind three real K3s nodes, embedded Flannel and exact images',
);
}
const database = report?.database;
if (
!exactKeys(database, [
'operator',
'operatorVersion',
'postgresVersionNumber',
'postgresImageId',
'instances',
'readyInstances',
'managerRole',
'migrationCount',
'controlCoreCapability',
'tlsVerified',
'primaryChangedDuringFailover',
]) ||
database?.operator !== 'cloudnative-pg' ||
!isToken(database?.operatorVersion, 64) ||
database?.postgresVersionNumber !== 180004 ||
!isDigest(database?.postgresImageId) ||
database?.instances !== 3 ||
database?.readyInstances !== 3 ||
database?.managerRole !== 'ql3_run_manager' ||
database?.migrationCount !== 57 ||
database?.controlCoreCapability !== 56 ||
database?.tlsVerified !== true ||
database?.primaryChangedDuringFailover !== true
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_DATABASE',
'three TLS CloudNativePG instances must run migration 57, capability 56 and the isolated Run manager role',
);
}
const deployment = report?.deployment;
if (
!exactKeys(deployment, [
'namespace',
'service',
'port',
'replicas',
'readyReplicas',
'podIdentitySha256',
'nodeIdentitySha256',
'serviceAccount',
'automountServiceAccountToken',
'requiredPodAntiAffinity',
'podDisruptionBudgetMinAvailable',
'maxUnavailable',
'maxConnectionsPerPod',
]) ||
deployment?.namespace !== 'qinglong3-system' ||
deployment?.service !== 'ql3-run-management' ||
deployment?.port !== 8448 ||
deployment?.replicas !== 2 ||
deployment?.readyReplicas !== 2 ||
!uniqueDigests(deployment?.podIdentitySha256, 2) ||
!uniqueDigests(deployment?.nodeIdentitySha256, 2) ||
deployment?.serviceAccount !== 'ql3-run-management' ||
deployment?.automountServiceAccountToken !== false ||
deployment?.requiredPodAntiAffinity !== true ||
deployment?.podDisruptionBudgetMinAvailable !== 1 ||
deployment?.maxUnavailable !== 0 ||
deployment?.maxConnectionsPerPod !== 2
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_DEPLOYMENT',
'two tokenless Run manager replicas must be ready on distinct nodes with the exact budget',
);
}
const client = report?.client;
if (
!exactKeys(client, [
'binary',
'operations',
'inputKind',
'inputImmutable',
'callerDrivenJob',
'backoffLimit',
'serviceAccountTokenMounted',
'rbacGranted',
'transportProtocol',
'mutualTls',
'servernameVerified',
'exactPodRequests',
'retryStatuses',
'stopStatuses',
'responseRedacted',
]) ||
client?.binary !== 'ql3-run-client' ||
JSON.stringify(client?.operations) !==
JSON.stringify(['run.retry', 'run.stop']) ||
client?.inputKind !== 'Secret' ||
client?.inputImmutable !== true ||
client?.callerDrivenJob !== true ||
client?.backoffLimit !== 0 ||
client?.serviceAccountTokenMounted !== false ||
client?.rbacGranted !== false ||
client?.transportProtocol !== 'TLSv1.3' ||
client?.mutualTls !== true ||
client?.servernameVerified !== true ||
client?.exactPodRequests !== 6 ||
JSON.stringify(client?.retryStatuses) !==
JSON.stringify(['accepted', 'existing', 'existing']) ||
JSON.stringify(client?.stopStatuses) !==
JSON.stringify(['accepted', 'already_requested', 'already_requested']) ||
client?.responseRedacted !== true
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_CLIENT',
'immutable caller-driven clients must retry and stop with exact replay across both Pods over TLS 1.3 mTLS',
);
}
if (
!allTrue(report?.identityRotation, [
'overlapOldAssertionAccepted',
'overlapNewAssertionAccepted',
'revokedOldAssertionRejected',
'activeNewAssertionAccepted',
'rollbackSurgeFailedClosed',
'twoReadyReplicasPreserved',
'durableGenerationReachedThree',
])
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_IDENTITY_ROTATION',
'identity overlap, revoke, rollback rejection and two-ready availability are mandatory',
);
}
const certificate = report?.certificateRotation;
if (
!exactKeys(certificate, [
'previousSerialSha256',
'currentSerialSha256',
'previousBundleSha256',
'currentBundleSha256',
'oldClientAcceptedBefore',
'replacementClientAcceptedBefore',
'oldClientRejectedAfter',
'replacementClientAcceptedAfter',
'fullPodReplacement',
'allReplicasReadyThroughout',
]) ||
!isDigest(certificate?.previousSerialSha256) ||
!isDigest(certificate?.currentSerialSha256) ||
certificate?.previousSerialSha256 === certificate?.currentSerialSha256 ||
!isDigest(certificate?.previousBundleSha256) ||
!isDigest(certificate?.currentBundleSha256) ||
certificate?.previousBundleSha256 === certificate?.currentBundleSha256 ||
![
'oldClientAcceptedBefore',
'replacementClientAcceptedBefore',
'oldClientRejectedAfter',
'replacementClientAcceptedAfter',
'fullPodReplacement',
'allReplicasReadyThroughout',
].every((key) => certificate?.[key] === true)
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_CERTIFICATE_ROTATION',
'CRL rotation must replace all Pods without dropping below two ready replicas',
);
}
if (
!allTrue(report?.availability, [
'databaseFailureWithdrewReadiness',
'databaseFailurePreservedLiveness',
'stalePodsDidNotRecoverInPlace',
'freshPodsRecoveredAfterDatabase',
'bothReplicasServedAfterRecovery',
])
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_AVAILABILITY',
'database loss must withdraw readiness, preserve liveness and require fresh manager Pods',
);
}
if (
!allTrue(report?.isolation, [
'labelledClientAllowed',
'unlabelledClientDenied',
'wrongPortDenied',
'kubernetesApiEgressDenied',
'publicInternetEgressDenied',
'cloudNativePgEgressAllowed',
'managerSecretReadDenied',
'managerMutationRbacDenied',
])
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_ISOLATION',
'CNI and Kubernetes RBAC least-privilege observations are incomplete',
);
}
const durability = report?.durability;
if (
!exactKeys(durability, [
'sourceRunStatus',
'retryRunCount',
'retryAttemptCount',
'retryEventCount',
'stoppedRunCount',
'stopEventCount',
'allowedAuditCount',
'deniedAuditCount',
'duplicateMutationCount',
'identityGeneration',
'weakAuthenticationAuditCount',
'survivedCloudNativePgFailover',
]) ||
durability?.sourceRunStatus !== 'failed' ||
durability?.retryRunCount !== 1 ||
durability?.retryAttemptCount !== 1 ||
durability?.retryEventCount !== 2 ||
durability?.stoppedRunCount !== 1 ||
durability?.stopEventCount !== 1 ||
durability?.allowedAuditCount !== 2 ||
durability?.deniedAuditCount !== 1 ||
durability?.duplicateMutationCount !== 0 ||
durability?.identityGeneration !== 3 ||
durability?.weakAuthenticationAuditCount !== 0 ||
durability?.survivedCloudNativePgFailover !== true
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_DURABILITY',
'exact Run, Attempt, Event, cancellation and audit facts must survive failover without duplicates',
);
}
if (
!allTrue(report?.gates, [
'realThreeNodeKubernetes',
'realCniPolicy',
'threeInstanceCloudNativePg',
'twoManagerPodsOnDistinctNodes',
'tls13ProductClientAcrossBothPods',
'strongUserRetryAndStop',
'identityProjectionRotation',
'certificateRevocationRollout',
'databaseReadinessFence',
'durableFactsSurvivedFailover',
'leastPrivilege',
'passed',
])
) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_GATES',
'every independently observed release gate must pass',
);
}
if (JSON.stringify(report?.limitations) !== JSON.stringify(LIMITATIONS)) {
reject(
'QL3_RUN_MANAGEMENT_KUBERNETES_LIVE_LIMITATIONS',
'the exact non-production limitations must remain visible',
);
}
return Object.freeze({
schemaVersion: 1,
fixture: FIXTURE,
findings: Object.freeze(findings),
compatible: findings.length === 0,
});
}
function main(argv = process.argv.slice(2)) {
const argument = argv[0] === '--' ? argv.slice(1) : argv;
if (
argument.length !== 1 ||
!argument[0].startsWith('--report=') ||
!path.isAbsolute(argument[0].slice('--report='.length))
) {
throw new Error(
'usage: ql3-run-management-kubernetes-live-audit --report=/absolute/private-report.json',
);
}
const reportFile = argument[0].slice('--report='.length);
const stat = fs.lstatSync(reportFile);
if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o077) !== 0) {
throw new Error(
'Run management Kubernetes live report must be a private regular file',
);
}
const audit = validateRunManagementKubernetesLiveReport(
JSON.parse(fs.readFileSync(reportFile, 'utf8')),
);
process.stdout.write(JSON.stringify(audit, null, 2) + '\n');
if (!audit.compatible) process.exitCode = 1;
}
if (require.main === module) {
try {
main();
} catch (error) {
process.stderr.write(
'QL3 Run management Kubernetes live audit failed: ' +
(error instanceof Error ? error.message : String(error)) +
'\n',
);
process.exitCode = 1;
}
}
module.exports = {
FIXTURE,
LIMITATIONS,
validateRunManagementKubernetesLiveReport,
};
File diff suppressed because it is too large Load Diff