Files
qinglong/scripts/ql3-worker-credential-management-kubernetes-live-contract.cjs

2835 lines
91 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const assert = require('node:assert/strict');
const {
generateKeyPairSync,
randomBytes,
randomUUID,
sign,
} = require('node:crypto');
const fs = require('node:fs');
const yaml = require('js-yaml');
const os = require('node:os');
const path = require('node:path');
const { createRequire } = require('node:module');
const { spawnSync } = require('node:child_process');
const ROOT = path.resolve(__dirname, '..');
const K3S_IMAGE = 'rancher/k3s:v1.34.3-k3s1';
const K3S_DIGEST =
'sha256:71abd3a56f57884c62732e0e0d87606052cb5f8555b7db7e8e33c04570b8175c';
const POSTGRES_IMAGE = 'postgres:18';
const POSTGRES_DATABASE = 'ql3_contract';
const POSTGRES_SUPERUSER_PASSWORD = 'postgres';
const NAMESPACE = 'qinglong3-system';
const DEPLOYMENT = 'ql3-worker-credential-management';
const SERVICE = 'ql3-worker-credential-management';
const IDENTITY_SECRET = 'ql3-worker-credential-management-identity';
const TLS_SECRET = 'ql3-worker-credential-management-tls';
const DATABASE_SECRET = 'ql3-worker-credential-management-database';
const SERVERNAME = `${SERVICE}.${NAMESPACE}.svc`;
const MANAGEMENT_PATH = '/api/v3/worker-credentials/management';
const ISSUER = 'https://identity.qinglong.test/';
const AUDIENCE = 'qinglong3-worker-credential-management';
const POSTGRES_ROLES = Object.freeze({
migration: Object.freeze({
user: 'ql3_migration',
password: 'ql3_migration_test',
}),
runtime: Object.freeze({ user: 'ql3_runtime', password: 'ql3_runtime_test' }),
admin: Object.freeze({ user: 'ql3_admin', password: 'ql3_admin_test' }),
automationManager: Object.freeze({
user: 'ql3_automation_manager',
password: 'ql3_automation_manager_test',
}),
approvalManager: Object.freeze({
user: 'ql3_approval_manager',
password: 'ql3_approval_manager_test',
}),
packageManager: Object.freeze({
user: 'ql3_package_manager',
password: 'ql3_package_manager_test',
}),
packageExecutor: Object.freeze({
user: 'ql3_package_executor',
password: 'ql3_package_executor_test',
}),
workerCredentialManager: Object.freeze({
user: 'ql3_worker_credential_manager',
password: 'ql3_worker_credential_manager_test',
}),
workerCredentialExecutor: Object.freeze({
user: 'ql3_worker_credential_executor',
password: 'ql3_worker_credential_executor_test',
}),
workerIngress: Object.freeze({
user: 'ql3_worker_ingress',
password: 'ql3_worker_ingress_test',
}),
});
const clusterRequire = createRequire(
path.join(ROOT, 'packages/ql3-cluster-admin/package.json'),
);
const HEALTH_SCRIPT = String.raw`
const fs = require('node:fs');
const https = require('node:https');
const [host, rawPort] = process.argv.slice(1);
const ca = fs.readFileSync('/var/run/qinglong3/ca/ca.crt');
function request(path) {
return new Promise((resolve, reject) => {
const outgoing = https.request({
host,
port: Number(rawPort),
path,
method: 'GET',
servername: host,
ca,
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
rejectUnauthorized: true,
agent: false,
headers: { connection: 'close' }
}, (incoming) => {
const chunks = [];
incoming.on('data', (chunk) => chunks.push(chunk));
incoming.once('end', () => resolve({
path,
statusCode: incoming.statusCode,
protocol: incoming.socket.getProtocol(),
body: JSON.parse(Buffer.concat(chunks).toString('utf8'))
}));
});
outgoing.setTimeout(10000, () => outgoing.destroy(new Error('timeout')));
outgoing.once('error', reject);
outgoing.end();
});
}
(async () => {
let lastError = 'unavailable';
for (let attempt = 1; attempt <= 30; attempt += 1) {
try {
const responses = await Promise.all([request('/readyz'), request('/livez')]);
const output = JSON.stringify({ schemaVersion: 1, attempt, responses });
fs.writeFileSync('/dev/termination-log', output + '\n');
process.stdout.write(output + '\n');
return;
} catch (error) {
lastError = String(error.code || error.name || 'UNKNOWN').slice(0, 64);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
const output = JSON.stringify({ schemaVersion: 1, error: lastError });
fs.writeFileSync('/dev/termination-log', output + '\n');
process.stderr.write(output + '\n');
process.exitCode = 1;
})();
`;
const RAW_REQUEST_DIAGNOSTIC_SCRIPT = String.raw`
const fs = require('node:fs');
const https = require('node:https');
const [configFile, commandFile, assertionFile] = process.argv.slice(1);
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
const body = fs.readFileSync(commandFile);
const assertion = fs.readFileSync(assertionFile, 'ascii');
const target = new URL(config.endpoint);
const cert = config.clientCertificateFile
? fs.readFileSync(config.clientCertificateFile)
: undefined;
const key = config.clientPrivateKeyFile
? fs.readFileSync(config.clientPrivateKeyFile)
: undefined;
const outgoing = https.request({
protocol: target.protocol,
hostname: target.hostname,
port: Number(target.port),
path: target.pathname,
method: 'POST',
servername: config.servername,
ca: fs.readFileSync(config.caFile),
cert,
key,
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
rejectUnauthorized: true,
agent: false,
headers: {
accept: 'application/json',
'accept-encoding': 'identity',
authorization: 'Bearer ' + assertion,
connection: 'close',
'content-type': 'application/json',
'content-length': String(body.length)
}
}, (incoming) => {
const chunks = [];
incoming.on('data', (chunk) => chunks.push(chunk));
incoming.once('end', () => {
const responseBody = Buffer.concat(chunks).toString('utf8');
const output = JSON.stringify({
event: 'raw_request_completed',
statusCode: incoming.statusCode,
protocol: incoming.socket.getProtocol(),
rawHeaders: incoming.rawHeaders,
body: JSON.parse(responseBody)
});
fs.writeFileSync('/dev/termination-log', output + '\n');
process.stdout.write(output + '\n');
});
});
outgoing.setTimeout(10000, () => outgoing.destroy(new Error('timeout')));
outgoing.once('error', (error) => {
const output = JSON.stringify({
event: 'raw_request_failed',
code: String(error.code || error.name || 'UNKNOWN').slice(0, 64),
message: String(error.message || '').slice(0, 256)
});
fs.writeFileSync('/dev/termination-log', output + '\n');
process.stdout.write(output + '\n');
});
outgoing.end(body);
`;
const CONNECTIVITY_SCRIPT = String.raw`
const fs = require('node:fs');
const https = require('node:https');
const [host, rawPort] = process.argv.slice(1);
const ca = fs.readFileSync('/var/run/qinglong3/ca/ca.crt');
function probe() {
return new Promise((resolve, reject) => {
const outgoing = https.request({
host,
port: Number(rawPort),
path: '/readyz',
method: 'GET',
servername: host,
ca,
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
rejectUnauthorized: true,
agent: false,
headers: { connection: 'close' }
}, (incoming) => {
incoming.resume();
incoming.once('end', () => resolve({
statusCode: incoming.statusCode,
protocol: incoming.socket.getProtocol()
}));
});
outgoing.setTimeout(3000, () => outgoing.destroy(new Error('timeout')));
outgoing.once('error', reject);
outgoing.end();
});
}
(async () => {
let lastError = 'unavailable';
for (let attempt = 1; attempt <= 30; attempt += 1) {
try {
const response = await probe();
if (response.statusCode === 200 && response.protocol === 'TLSv1.3') {
const output = JSON.stringify({
event: 'connectivity_ready',
attempts: attempt,
...response
});
fs.writeFileSync('/dev/termination-log', output + '\n');
process.stdout.write(output + '\n');
return;
}
lastError = 'HTTP_' + String(response.statusCode);
} catch (error) {
lastError = String(error.code || error.name || 'UNKNOWN').slice(0, 64);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
const output = JSON.stringify({ event: 'connectivity_failed', error: lastError });
fs.writeFileSync('/dev/termination-log', output + '\n');
process.stderr.write(output + '\n');
process.exitCode = 1;
})();
`;
function run(binary, args, options = {}) {
if (!options.quiet) {
process.stderr.write(`+ ${path.basename(binary)} ${args.join(' ')}\n`);
}
const result = spawnSync(binary, args, {
cwd: ROOT,
env: options.env ?? process.env,
input: options.input,
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
stdio: options.capture
? ['pipe', 'pipe', 'pipe']
: [
options.input === undefined ? 'inherit' : 'pipe',
'inherit',
'inherit',
],
});
if (result.error) throw result.error;
if (result.status !== 0 && !options.allowFailure) {
throw new Error(
`${path.basename(binary)} failed with ${String(result.status)}: ` +
`${result.stderr || result.stdout || ''}`,
);
}
return Object.freeze({
status: result.status,
stdout: options.capture ? result.stdout.trim() : '',
stderr: options.capture ? result.stderr.trim() : '',
});
}
function sleep(milliseconds) {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
async function waitFor(description, timeoutMs, inspect) {
const startedAt = Date.now();
let last = 'not observed';
while (Date.now() - startedAt < timeoutMs) {
try {
const value = await inspect();
if (value?.ready) return value.value;
if (value?.fact) last = value.fact;
} catch (error) {
last = error instanceof Error ? error.message : String(error);
}
await sleep(500);
}
throw new Error(`${description} timed out: ${last}`);
}
function reviewedKey(kid) {
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
return Object.freeze({
kid,
privateKey,
publicJwk: Object.freeze({
...publicKey.export({ format: 'jwk' }),
alg: 'EdDSA',
kid,
use: 'sig',
}),
});
}
function keyset(generation, keys, revokedKids = []) {
return Object.freeze({
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, subject = 'operator-a', suffix = randomUUID()) {
const now = Math.floor(Date.now() / 1_000);
const header = Buffer.from(
JSON.stringify({
alg: 'EdDSA',
kid: key.kid,
typ: 'ql3-worker-credential-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 + 240,
iat: now,
iss: ISSUER,
jti: `ql3-worker-manager-live-${suffix}`,
ql3_purpose: 'worker-credential-management',
sub: subject,
}),
).toString('base64url');
const signed = `${header}.${payload}`;
return `${signed}.${sign(
null,
Buffer.from(signed, 'ascii'),
key.privateKey,
).toString('base64url')}`;
}
function planCommand(index, nowMs = Date.now()) {
return Object.freeze({
schemaVersion: 1,
operation: 'worker-credential.plan',
request: Object.freeze({
action: 'issue',
actionRef: `worker-credential:manager-live:plan-${index}`,
authorityProjectId: 'cluster-authority',
credentialExpiresAtMs: nowMs + 60 * 60_000,
credentialId: `manager_live_credential_${index}`,
credentialNotBeforeAtMs: nowMs + 5 * 60_000,
deliveryId: randomUUID(),
deploymentGeneration: `generation-manager_live_${index}`,
deploymentTargetDigest: 'a'.repeat(64),
previousCredentialId: null,
workerId: `manager-live-worker-${index}`,
}),
});
}
function inspectCommand(actionRef, suffix = randomUUID()) {
return Object.freeze({
schemaVersion: 1,
operation: 'worker-credential.inspect',
request: Object.freeze({
actionRef,
authorityProjectId: 'cluster-authority',
approvalRequestId: `approval-manager-live-${suffix}`,
inspectionId: `inspection-manager-live-${suffix}`,
}),
});
}
async function main() {
const docker = process.env.QL3_DOCKER_BIN || 'docker';
const kubectlBinary = process.env.QL3_KUBECTL_BIN || 'kubectl';
const suffix = process.pid.toString(36);
const network = `ql3-wcm-live-${suffix}`;
const server = `ql3-wcm-server-${suffix}`;
const agents = [`ql3-wcm-agent-a-${suffix}`, `ql3-wcm-agent-b-${suffix}`];
const postgres = `ql3-wcm-postgres-${suffix}`;
const containers = [server, ...agents, postgres];
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-wcm-live-'));
const kubeconfig = path.join(temporary, 'kubeconfig');
const imageArchive = path.join(temporary, 'admin-image.tar');
const adminImage = `ql3-worker-credential-manager-live:${suffix}`;
const caKey = path.join(temporary, 'ca.key');
const caCertificate = path.join(temporary, 'ca.crt');
const serverKey = path.join(temporary, 'tls.key');
const serverRequest = path.join(temporary, 'tls.csr');
const serverCertificate = path.join(temporary, 'tls.crt');
const serverExtensions = path.join(temporary, 'server.ext');
const clientOldKey = path.join(temporary, 'client-old.key');
const clientOldRequest = path.join(temporary, 'client-old.csr');
const clientOldCertificate = path.join(temporary, 'client-old.crt');
const clientNewKey = path.join(temporary, 'client-new.key');
const clientNewRequest = path.join(temporary, 'client-new.csr');
const clientNewCertificate = path.join(temporary, 'client-new.crt');
const clientCertificateRevocationList = path.join(temporary, 'client.crl');
const caConfig = path.join(temporary, 'ca.cnf');
const caDatabase = path.join(temporary, 'ca.index');
const caSerial = path.join(temporary, 'ca.serial');
const caCrlNumber = path.join(temporary, 'ca.crlnumber');
const caNewCertificates = path.join(temporary, 'ca-new-certificates');
const clusterToken = randomBytes(32).toString('base64url');
let networkCreated = false;
const createdContainers = new Set();
let adminImageBuilt = false;
let migrationDatabase;
try {
run(docker, ['version'], { capture: true, quiet: true });
const k3sImage = JSON.parse(
run(docker, ['image', 'inspect', K3S_IMAGE], {
capture: true,
quiet: true,
}).stdout,
)[0];
assert.ok(k3sImage.RepoDigests.includes(`rancher/k3s@${K3S_DIGEST}`));
const postgresImage = JSON.parse(
run(docker, ['image', 'inspect', POSTGRES_IMAGE], {
capture: true,
quiet: true,
}).stdout,
)[0];
for (const name of containers) {
assert.equal(
run(docker, ['inspect', name], {
capture: true,
quiet: true,
allowFailure: true,
}).status,
1,
`refusing to reuse Docker container ${name}`,
);
}
assert.equal(
run(docker, ['network', 'inspect', network], {
capture: true,
quiet: true,
allowFailure: true,
}).status,
1,
'refusing to reuse Docker network',
);
run(docker, ['network', 'create', network], { capture: true, quiet: true });
networkCreated = true;
run(
docker,
[
'run',
'-d',
'--privileged',
'--network',
network,
'--name',
server,
'-p',
'127.0.0.1::6443',
K3S_IMAGE,
'server',
'--token',
clusterToken,
'--node-name',
server,
'--disable=traefik',
'--disable=servicelb',
'--write-kubeconfig-mode=600',
'--tls-san=127.0.0.1',
],
{ capture: true, quiet: true },
);
createdContainers.add(server);
await waitFor('K3s server readiness', 120_000, () => {
const result = run(
docker,
['exec', server, 'kubectl', 'get', '--raw=/readyz'],
{ capture: true, quiet: true, allowFailure: true },
);
return result.status === 0 && result.stdout === 'ok'
? { ready: true, value: true }
: { ready: false, fact: result.stderr || result.stdout };
});
for (const agent of agents) {
run(
docker,
[
'run',
'-d',
'--privileged',
'--network',
network,
'--name',
agent,
K3S_IMAGE,
'agent',
'--server',
`https://${server}:6443`,
'--token',
clusterToken,
'--node-name',
agent,
],
{ capture: true, quiet: true },
);
createdContainers.add(agent);
}
const port = run(docker, ['port', server, '6443/tcp'], {
capture: true,
quiet: true,
}).stdout;
assert.match(port, /^127\.0\.0\.1:\d+$/);
const config = run(
docker,
['exec', server, 'cat', '/etc/rancher/k3s/k3s.yaml'],
{ capture: true, quiet: true },
).stdout.replace('https://127.0.0.1:6443', `https://${port}`);
fs.writeFileSync(kubeconfig, `${config}\n`, { mode: 0o600, flag: 'wx' });
const kubectl = (args, options = {}) =>
run(kubectlBinary, ['--kubeconfig', kubeconfig, ...args], options);
const kubectlJson = (args) =>
JSON.parse(
kubectl([...args, '-o', 'json'], {
capture: true,
quiet: true,
}).stdout,
);
const apply = (manifest) =>
kubectl(['apply', '-f', '-'], {
input: `${JSON.stringify(manifest)}\n`,
capture: true,
quiet: true,
});
const create = (manifest) =>
kubectl(['create', '-f', '-'], {
input: `${JSON.stringify(manifest)}\n`,
capture: true,
quiet: true,
});
const loadOperationManifest = (name) =>
yaml.load(
fs.readFileSync(
path.join(
ROOT,
'deploy/kubernetes/ql3-cluster/operations/' +
'worker-credential-management-client/base',
name,
),
'utf8',
),
);
await waitFor('three K3s nodes', 180_000, () => {
const nodes = kubectlJson(['get', 'nodes']).items ?? [];
const ready = nodes.filter((node) =>
node.status.conditions?.some(
(condition) =>
condition.type === 'Ready' && condition.status === 'True',
),
);
return ready.length === 3
? { ready: true, value: ready }
: { ready: false, fact: `${ready.length}/3 Ready nodes` };
});
const sourceRevision = run('git', ['rev-parse', 'HEAD'], {
capture: true,
quiet: true,
}).stdout;
run(docker, [
'build',
'--file',
'deploy/containers/ql3-cluster-admin/Dockerfile',
'--tag',
adminImage,
'--build-arg',
`SOURCE_REVISION=${sourceRevision}`,
'.',
]);
adminImageBuilt = true;
run(docker, ['image', 'save', '--output', imageArchive, adminImage]);
for (const node of [server, ...agents]) {
run(docker, ['cp', imageArchive, `${node}:/tmp/ql3-admin.tar`], {
capture: true,
quiet: true,
});
run(
docker,
[
'exec',
node,
'ctr',
'--address',
'/run/k3s/containerd/containerd.sock',
'--namespace',
'k8s.io',
'images',
'import',
'/tmp/ql3-admin.tar',
],
{ capture: true, quiet: true },
);
run(docker, ['exec', node, 'rm', '-f', '/tmp/ql3-admin.tar'], {
capture: true,
quiet: true,
});
}
fs.unlinkSync(imageArchive);
fs.writeFileSync(
serverExtensions,
[
'basicConstraints=CA:FALSE',
'keyUsage=digitalSignature,keyEncipherment',
'extendedKeyUsage=serverAuth',
`subjectAltName=DNS:${SERVERNAME},DNS:${SERVERNAME}.cluster.local`,
'',
].join('\n'),
{ mode: 0o600, flag: 'wx' },
);
run(
'openssl',
[
'req',
'-x509',
'-newkey',
'rsa:2048',
'-nodes',
'-days',
'1',
'-subj',
'/CN=QL3 Worker Manager Live CA',
'-keyout',
caKey,
'-out',
caCertificate,
],
{ capture: true, quiet: true },
);
fs.mkdirSync(caNewCertificates, { mode: 0o700 });
fs.writeFileSync(caDatabase, '', { mode: 0o600, flag: 'wx' });
fs.writeFileSync(caSerial, '1000\n', { mode: 0o600, flag: 'wx' });
fs.writeFileSync(caCrlNumber, '1000\n', { mode: 0o600, flag: 'wx' });
fs.writeFileSync(
caConfig,
[
'[ca]',
'default_ca=client_ca',
'[client_ca]',
`database=${caDatabase}`,
`new_certs_dir=${caNewCertificates}`,
`certificate=${caCertificate}`,
`private_key=${caKey}`,
`serial=${caSerial}`,
`crlnumber=${caCrlNumber}`,
'default_md=sha256',
'default_days=1',
'default_crl_days=1',
'policy=client_policy',
'unique_subject=no',
'copy_extensions=none',
'[client_policy]',
'commonName=supplied',
'[client_certificate]',
'basicConstraints=critical,CA:FALSE',
'keyUsage=critical,digitalSignature,keyEncipherment',
'extendedKeyUsage=clientAuth',
'',
].join('\n'),
{ mode: 0o600, flag: 'wx' },
);
for (const [commonName, keyFile, requestFile, certificateFile] of [
[
'ql3-worker-credential-management-client-old',
clientOldKey,
clientOldRequest,
clientOldCertificate,
],
[
'ql3-worker-credential-management-client-new',
clientNewKey,
clientNewRequest,
clientNewCertificate,
],
]) {
run(
'openssl',
[
'req',
'-newkey',
'rsa:2048',
'-nodes',
'-subj',
`/CN=${commonName}`,
'-keyout',
keyFile,
'-out',
requestFile,
],
{ capture: true, quiet: true },
);
run(
'openssl',
[
'ca',
'-batch',
'-notext',
'-config',
caConfig,
'-extensions',
'client_certificate',
'-in',
requestFile,
'-out',
certificateFile,
],
{ capture: true, quiet: true },
);
}
run(
'openssl',
[
'ca',
'-gencrl',
'-config',
caConfig,
'-out',
clientCertificateRevocationList,
],
{ capture: true, quiet: true },
);
run(
'openssl',
[
'req',
'-newkey',
'rsa:2048',
'-nodes',
'-subj',
`/CN=${SERVERNAME}`,
'-keyout',
serverKey,
'-out',
serverRequest,
],
{ capture: true, quiet: true },
);
run(
'openssl',
[
'x509',
'-req',
'-days',
'1',
'-in',
serverRequest,
'-CA',
caCertificate,
'-CAkey',
caKey,
'-CAcreateserial',
'-extfile',
serverExtensions,
'-out',
serverCertificate,
],
{ capture: true, quiet: true },
);
run(
docker,
[
'run',
'-d',
'--network',
network,
'--name',
postgres,
'-p',
'127.0.0.1::5432',
'-e',
`POSTGRES_DB=${POSTGRES_DATABASE}`,
'-e',
'POSTGRES_USER=postgres',
'-e',
`POSTGRES_PASSWORD=${POSTGRES_SUPERUSER_PASSWORD}`,
POSTGRES_IMAGE,
],
{ capture: true, quiet: true },
);
createdContainers.add(postgres);
await waitFor('PostgreSQL final TCP readiness', 60_000, () => {
const result = run(
docker,
[
'exec',
postgres,
'pg_isready',
'-h',
'127.0.0.1',
'-U',
'postgres',
'-d',
POSTGRES_DATABASE,
],
{ capture: true, quiet: true, allowFailure: true },
);
return result.status === 0
? { ready: true, value: true }
: { ready: false, fact: result.stderr || result.stdout };
});
const postgresEndpoint = run(docker, ['port', postgres, '5432/tcp'], {
capture: true,
quiet: true,
}).stdout;
assert.match(postgresEndpoint, /^127\.0\.0\.1:\d+$/);
const postgresPort = Number(
postgresEndpoint.slice(postgresEndpoint.lastIndexOf(':') + 1),
);
const postgresAddress = run(
docker,
[
'inspect',
'--format',
'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}',
postgres,
],
{ capture: true, quiet: true },
).stdout;
assert.match(postgresAddress, /^\d{1,3}(?:\.\d{1,3}){3}$/);
const { createPostgresDatabaseOpener } = clusterRequire(
'@qinglong/cluster-postgres/worker-credential-manager',
);
const { runPostgresMigrations } = clusterRequire(
'@qinglong/cluster-postgres/migration',
);
const databaseUrl = ({ user, password }) =>
`postgresql://${encodeURIComponent(user)}:${encodeURIComponent(
password,
)}` + `@127.0.0.1:${postgresPort}/${POSTGRES_DATABASE}`;
const openDatabase = (role, credential, applicationName) =>
createPostgresDatabaseOpener({
role,
connection: {
connectionString: databaseUrl(credential),
tls: { mode: 'disable' },
},
pool: {
applicationName,
maxConnections: 1,
connectionTimeoutMs: 2_000,
},
onPoolError() {},
});
const openMigrationDatabase = openDatabase(
'migration',
POSTGRES_ROLES.migration,
'ql3-worker-manager-live-migration',
);
const bootstrap = await openDatabase(
'migration',
{ user: 'postgres', password: POSTGRES_SUPERUSER_PASSWORD },
'ql3-worker-manager-live-bootstrap',
)();
try {
for (const credential of Object.values(POSTGRES_ROLES)) {
await bootstrap.pool.query(
`CREATE ROLE ${credential.user} LOGIN PASSWORD '${credential.password}'`,
);
}
await bootstrap.pool.query(
`ALTER DATABASE ${POSTGRES_DATABASE} OWNER TO ` +
POSTGRES_ROLES.migration.user,
);
} finally {
await bootstrap.close();
}
migrationDatabase = await openMigrationDatabase();
await runPostgresMigrations({ pool: migrationDatabase.pool });
const seededAtMs = Date.now();
await migrationDatabase.pool.query(
`INSERT INTO "ql3"."projects" (
id, name, slug, status, version, created_at_ms, updated_at_ms
) VALUES (
'cluster-authority', 'Cluster Authority', 'cluster-authority',
'active', 1, $1, $1
)`,
[seededAtMs],
);
await migrationDatabase.pool.query(
`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 (
'cluster-authority', 'user', 'operator-a', 1, 'active', 'admin',
'binding-operator-a-v1', 'user', 'owner-a', $1
)`,
[seededAtMs],
);
const oldKey = reviewedKey('worker-manager-key-1');
const newKey = reviewedKey('worker-manager-key-2');
const keyset1 = keyset(1, [oldKey]);
const keyset2 = keyset(2, [oldKey, newKey]);
const keyset3 = keyset(3, [oldKey, newKey], [oldKey.kid]);
const caText = fs.readFileSync(caCertificate, 'utf8');
const tlsCertificateText = fs.readFileSync(serverCertificate, 'utf8');
const tlsKeyText = fs.readFileSync(serverKey, 'utf8');
const clientOldCertificateText = fs.readFileSync(
clientOldCertificate,
'utf8',
);
const clientOldKeyText = fs.readFileSync(clientOldKey, 'utf8');
const clientNewCertificateText = fs.readFileSync(
clientNewCertificate,
'utf8',
);
const clientNewKeyText = fs.readFileSync(clientNewKey, 'utf8');
let activeClientCertificateText = clientOldCertificateText;
let activeClientKeyText = clientOldKeyText;
let clientCrlText = fs.readFileSync(
clientCertificateRevocationList,
'utf8',
);
const managerUrl =
`postgresql://${encodeURIComponent(
POSTGRES_ROLES.workerCredentialManager.user,
)}` +
`:${encodeURIComponent(
POSTGRES_ROLES.workerCredentialManager.password,
)}` +
`@${postgresAddress}:5432/${POSTGRES_DATABASE}`;
apply({
apiVersion: 'v1',
kind: 'Namespace',
metadata: { name: NAMESPACE },
});
apply({
apiVersion: 'v1',
kind: 'Secret',
metadata: { name: TLS_SECRET, namespace: NAMESPACE },
type: 'kubernetes.io/tls',
stringData: {
'tls.crt': tlsCertificateText,
'tls.key': tlsKeyText,
'ca.crt': caText,
'client.crl': clientCrlText,
},
});
const applyIdentity = (document) =>
apply({
apiVersion: 'v1',
kind: 'Secret',
metadata: { name: IDENTITY_SECRET, namespace: NAMESPACE },
type: 'Opaque',
stringData: { 'keyset.json': `${JSON.stringify(document)}\n` },
});
applyIdentity(keyset1);
apply({
apiVersion: 'v1',
kind: 'Secret',
metadata: { name: DATABASE_SECRET, namespace: NAMESPACE },
type: 'Opaque',
stringData: { 'postgres-worker-credential-manager-url': managerUrl },
});
apply({
apiVersion: 'v1',
kind: 'ServiceAccount',
metadata: { name: DEPLOYMENT, namespace: NAMESPACE },
automountServiceAccountToken: false,
});
apply({
apiVersion: 'v1',
kind: 'Service',
metadata: { name: SERVICE, namespace: NAMESPACE },
spec: {
type: 'ClusterIP',
selector: {
'app.kubernetes.io/name': DEPLOYMENT,
'app.kubernetes.io/component': 'worker-credential-management',
},
ports: [
{ name: 'https', port: 8444, targetPort: 'https', protocol: 'TCP' },
],
},
});
apply({
apiVersion: 'policy/v1',
kind: 'PodDisruptionBudget',
metadata: { name: DEPLOYMENT, namespace: NAMESPACE },
spec: {
minAvailable: 1,
selector: {
matchLabels: {
'app.kubernetes.io/name': DEPLOYMENT,
'app.kubernetes.io/component': 'worker-credential-management',
},
},
},
});
apply({
apiVersion: 'networking.k8s.io/v1',
kind: 'NetworkPolicy',
metadata: { name: DEPLOYMENT, namespace: NAMESPACE },
spec: {
podSelector: {
matchLabels: {
'app.kubernetes.io/name': DEPLOYMENT,
'app.kubernetes.io/component': 'worker-credential-management',
},
},
policyTypes: ['Ingress', 'Egress'],
ingress: [
{
from: [
{
podSelector: {
matchLabels: {
'qinglong.io/worker-credential-management-client': 'true',
},
},
},
],
ports: [{ protocol: 'TCP', port: 8444 }],
},
],
egress: [
{
to: [{ ipBlock: { cidr: `${postgresAddress}/32` } }],
ports: [{ protocol: 'TCP', port: 5432 }],
},
],
},
});
apply({
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: { name: DEPLOYMENT, namespace: NAMESPACE },
spec: {
replicas: 2,
minReadySeconds: 2,
revisionHistoryLimit: 3,
progressDeadlineSeconds: 300,
strategy: {
type: 'RollingUpdate',
rollingUpdate: { maxUnavailable: 0, maxSurge: 1 },
},
selector: {
matchLabels: {
'app.kubernetes.io/name': DEPLOYMENT,
'app.kubernetes.io/component': 'worker-credential-management',
},
},
template: {
metadata: {
labels: {
'app.kubernetes.io/name': DEPLOYMENT,
'app.kubernetes.io/component': 'worker-credential-management',
'app.kubernetes.io/part-of': 'qinglong3',
},
annotations: { 'qinglong.io/identity-generation': '1' },
},
spec: {
serviceAccountName: DEPLOYMENT,
automountServiceAccountToken: false,
terminationGracePeriodSeconds: 10,
securityContext: {
runAsNonRoot: true,
runAsUser: 10001,
runAsGroup: 10001,
fsGroup: 10001,
seccompProfile: { type: 'RuntimeDefault' },
},
affinity: {
podAntiAffinity: {
requiredDuringSchedulingIgnoredDuringExecution: [
{
topologyKey: 'kubernetes.io/hostname',
labelSelector: {
matchLabels: {
'app.kubernetes.io/name': DEPLOYMENT,
'app.kubernetes.io/component':
'worker-credential-management',
},
},
},
],
},
},
containers: [
{
name: 'management',
image: adminImage,
imagePullPolicy: 'Never',
command: [
'node',
'/opt/qinglong/node_modules/@qinglong/cluster-admin/dist/' +
'worker-credential/management-server/workerCredentialManagementCli.js',
],
securityContext: {
allowPrivilegeEscalation: false,
readOnlyRootFilesystem: true,
capabilities: { drop: ['ALL'] },
},
env: [
{ name: 'QL3_PROFILE', value: 'cluster-admin' },
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_ENABLED',
value: 'true',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_HOST',
value: '0.0.0.0',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_PORT',
value: '8444',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_TLS_CERT_FILE',
value: '/var/run/secrets/qinglong3/management-tls/tls.crt',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_TLS_KEY_FILE',
value: '/var/run/secrets/qinglong3/management-tls/tls.key',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_CLIENT_CA_FILE',
value: '/var/run/secrets/qinglong3/management-tls/ca.crt',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_CLIENT_CRL_FILE',
value:
'/var/run/secrets/qinglong3/management-tls/client.crl',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_IDENTITY_KEYSET_FILE',
value: '/var/run/qinglong3/management-identity/keyset.json',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_QUOTA_WINDOW_MS',
value: '300000',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_PLAN_QUOTA',
value: '8',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_PROPOSE_QUOTA',
value: '32',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_DECIDE_QUOTA',
value: '32',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_INSPECT_QUOTA',
value: '128',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_MAX_CONNECTIONS',
value: '64',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_MAX_CONCURRENT_REQUESTS',
value: '32',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_RATE_WINDOW_MS',
value: '300000',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_PEER_REQUEST_LIMIT',
value: '128',
},
{
name: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_GLOBAL_REQUEST_LIMIT',
value: '10000',
},
{
name: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_URL',
valueFrom: {
secretKeyRef: {
name: DATABASE_SECRET,
key: 'postgres-worker-credential-manager-url',
},
},
},
{
name: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_TLS_MODE',
value: 'disable',
},
{
name: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_ALLOW_INSECURE',
value: 'true',
},
{
name: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_MAX_CONNECTIONS',
value: '2',
},
{
name: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_APPLICATION_NAME',
value: 'ql3-worker-manager-live',
},
],
ports: [
{ name: 'https', containerPort: 8444, protocol: 'TCP' },
],
startupProbe: {
httpGet: { path: '/livez', port: 'https', scheme: 'HTTPS' },
periodSeconds: 2,
timeoutSeconds: 1,
failureThreshold: 45,
},
readinessProbe: {
httpGet: { path: '/readyz', port: 'https', scheme: 'HTTPS' },
periodSeconds: 2,
timeoutSeconds: 1,
failureThreshold: 2,
},
livenessProbe: {
httpGet: { path: '/livez', port: 'https', scheme: 'HTTPS' },
periodSeconds: 10,
timeoutSeconds: 2,
failureThreshold: 3,
},
resources: {
requests: { cpu: '50m', memory: '96Mi' },
limits: { cpu: '1', memory: '384Mi' },
},
volumeMounts: [
{ name: 'tmp', mountPath: '/tmp' },
{
name: 'management-tls',
mountPath: '/var/run/secrets/qinglong3/management-tls',
readOnly: true,
},
{
name: 'management-identity',
mountPath: '/var/run/qinglong3/management-identity',
readOnly: true,
},
],
},
],
volumes: [
{
name: 'tmp',
emptyDir: { medium: 'Memory', sizeLimit: '16Mi' },
},
{
name: 'management-tls',
secret: {
secretName: TLS_SECRET,
defaultMode: 288,
items: [
{ key: 'tls.crt', path: 'tls.crt' },
{ key: 'tls.key', path: 'tls.key' },
{ key: 'ca.crt', path: 'ca.crt' },
{ key: 'client.crl', path: 'client.crl' },
],
},
},
{
name: 'management-identity',
secret: {
secretName: IDENTITY_SECRET,
defaultMode: 292,
items: [{ key: 'keyset.json', path: 'keyset.json' }],
},
},
],
},
},
},
});
const readyManagerPods = async (excludedUids = new Set()) =>
waitFor('two Ready manager Pods on distinct nodes', 180_000, () => {
const pods = kubectlJson([
'-n',
NAMESPACE,
'get',
'pods',
'-l',
`app.kubernetes.io/name=${DEPLOYMENT}`,
]).items.filter(
(pod) =>
pod.metadata.deletionTimestamp === undefined &&
!excludedUids.has(pod.metadata.uid) &&
pod.status.conditions?.some(
(condition) =>
condition.type === 'Ready' && condition.status === 'True',
),
);
const nodes = new Set(pods.map((pod) => pod.spec.nodeName));
return pods.length === 2 && nodes.size === 2
? {
ready: true,
value: pods.sort((a, b) =>
a.metadata.name.localeCompare(b.metadata.name),
),
}
: {
ready: false,
fact: `${pods.length} Ready Pods on ${nodes.size} nodes`,
};
});
const patchGeneration = (generation) =>
kubectl(
[
'-n',
NAMESPACE,
'patch',
'deployment',
DEPLOYMENT,
'--type=merge',
'-p',
JSON.stringify({
spec: {
template: {
metadata: {
annotations: {
'qinglong.io/identity-generation': generation,
},
},
},
},
}),
],
{ capture: true, quiet: true },
);
const waitRollout = () =>
kubectl(
[
'-n',
NAMESPACE,
'rollout',
'status',
`deployment/${DEPLOYMENT}`,
'--timeout=180s',
],
{ capture: true, quiet: true },
);
waitRollout();
const generation1Pods = await readyManagerPods();
assert.equal(
new Set(generation1Pods.map((pod) => pod.spec.nodeName)).size,
2,
);
for (const pod of generation1Pods) {
assert.equal(pod.spec.automountServiceAccountToken, false);
assert.equal(pod.spec.serviceAccountName, DEPLOYMENT);
assert.equal(
pod.spec.volumes.some((volume) =>
volume.projected?.sources?.some(
(source) => source.serviceAccountToken !== undefined,
),
),
false,
);
}
const clientShell = [
'umask 077',
'cp /var/run/qinglong3/client/client.json /tmp/client.json',
'cp /var/run/qinglong3/client/command.json /tmp/command.json',
'cp /var/run/qinglong3/client/assertion.jwt /tmp/assertion.jwt',
'cp /var/run/qinglong3/client/ca.crt /tmp/ca.crt',
'cp /var/run/qinglong3/client/client.crt /tmp/client.crt',
'cp /var/run/qinglong3/client/client.key /tmp/client.key',
'chmod 600 /tmp/client.json /tmp/command.json /tmp/assertion.jwt ' +
'/tmp/ca.crt /tmp/client.crt /tmp/client.key',
'set +e',
'attempt=0',
'while true; do',
' attempt=$((attempt + 1))',
' output="$(node /opt/qinglong/node_modules/@qinglong/cluster-admin/dist/' +
'worker-credential/workerCredentialManagementClientCli.js ' +
'--config=/tmp/client.json --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 || ' +
'[ "$attempt" -ge 12 ]; then',
' break',
' fi',
' sleep 1',
'done',
'if [ "$status" -ne 0 ] && printf \'%s\' "$output" | ' +
'grep -q QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_REQUEST_FAILED; then',
' diagnostic="$(node -e "$QL3_RAW_REQUEST_DIAGNOSTIC_SCRIPT" -- ' +
'/tmp/client.json /tmp/command.json /tmp/assertion.jwt 2>&1)"',
' output="{\\"client\\":$output,\\"diagnostic\\":$diagnostic}"',
'fi',
'printf \'{"transportAttempts":%s}\\n%s\\n\' ' +
'"$attempt" "$output" > /dev/termination-log',
'printf \'%s\\n\' "$output"',
'exit "$status"',
].join('\n');
const clientConfig = Object.freeze({
schemaVersion: 1,
endpoint: `https://${SERVERNAME}:8444${MANAGEMENT_PATH}`,
servername: SERVERNAME,
caFile: '/tmp/ca.crt',
clientCertificateFile: '/tmp/client.crt',
clientPrivateKeyFile: '/tmp/client.key',
requestTimeoutMs: 15_000,
});
const createClientJob = ({
name,
target,
command,
bearer,
clientCertificateText = activeClientCertificateText,
clientKeyText = activeClientKeyText,
}) => {
const inputSecret = `${name}-input`;
create({
apiVersion: 'v1',
kind: 'Secret',
metadata: { name: inputSecret, namespace: NAMESPACE },
type: 'Opaque',
stringData: {
'client.json': `${JSON.stringify(clientConfig)}\n`,
'command.json': `${JSON.stringify(command)}\n`,
'assertion.jwt': bearer,
'ca.crt': caText,
'client.crt': clientCertificateText,
'client.key': clientKeyText,
},
});
create({
apiVersion: 'batch/v1',
kind: 'Job',
metadata: { name, namespace: NAMESPACE },
spec: {
backoffLimit: 0,
activeDeadlineSeconds: 60,
ttlSecondsAfterFinished: 600,
template: {
metadata: {
labels: {
app: 'ql3-worker-manager-live-client',
'qinglong.io/worker-credential-management-client': 'true',
},
},
spec: {
automountServiceAccountToken: false,
restartPolicy: 'Never',
hostAliases: [
{ ip: target.status.podIP, hostnames: [SERVERNAME] },
],
securityContext: {
runAsNonRoot: true,
runAsUser: 10001,
runAsGroup: 10001,
fsGroup: 10001,
seccompProfile: { type: 'RuntimeDefault' },
},
containers: [
{
name: 'client',
image: adminImage,
imagePullPolicy: 'Never',
command: ['/bin/sh', '-c'],
args: [clientShell],
env: [
{
name: 'QL3_RAW_REQUEST_DIAGNOSTIC_SCRIPT',
value: RAW_REQUEST_DIAGNOSTIC_SCRIPT,
},
],
terminationMessagePolicy: 'File',
securityContext: {
allowPrivilegeEscalation: false,
readOnlyRootFilesystem: true,
capabilities: { drop: ['ALL'] },
},
resources: {
requests: { cpu: '10m', memory: '32Mi' },
limits: { cpu: '250m', memory: '128Mi' },
},
volumeMounts: [
{ name: 'tmp', mountPath: '/tmp' },
{
name: 'client-input',
mountPath: '/var/run/qinglong3/client',
readOnly: true,
},
],
},
],
volumes: [
{
name: 'tmp',
emptyDir: { medium: 'Memory', sizeLimit: '4Mi' },
},
{
name: 'client-input',
secret: { secretName: inputSecret, defaultMode: 288 },
},
],
},
},
},
});
return Object.freeze({ name, target, command, bearer });
};
const waitClientJob = async (job, expected) => {
const completion = await waitFor(
`${job.name} completion`,
120_000,
() => {
const current = kubectlJson([
'-n',
NAMESPACE,
'get',
'job',
job.name,
]);
const complete = current.status.conditions?.some(
(condition) =>
condition.type === 'Complete' && condition.status === 'True',
);
const failed = current.status.conditions?.some(
(condition) =>
condition.type === 'Failed' && condition.status === 'True',
);
return complete || failed
? { ready: true, value: { complete, failed } }
: { ready: false, fact: JSON.stringify(current.status ?? {}) };
},
);
const pods = kubectlJson([
'-n',
NAMESPACE,
'get',
'pods',
'-l',
`job-name=${job.name}`,
]).items;
assert.equal(pods.length, 1);
const terminated =
pods[0].status.containerStatuses?.[0]?.state?.terminated;
const outputText = terminated?.message ?? '';
assert.equal(outputText.includes(job.bearer), false);
assert.equal(
outputText.includes(POSTGRES_ROLES.workerCredentialManager.password),
false,
);
const outputLines = outputText.split('\n').filter(Boolean);
const attemptFact = JSON.parse(outputLines[0]);
assert.ok(Number.isSafeInteger(attemptFact.transportAttempts));
assert.ok(attemptFact.transportAttempts >= 1);
assert.ok(attemptFact.transportAttempts <= 12);
const output = JSON.parse(outputLines.at(-1));
const targetFailureEvidence = () => {
const state = kubectl(
[
'-n',
NAMESPACE,
'get',
'pod',
job.target.metadata.name,
'-o',
'json',
],
{ capture: true, quiet: true, allowFailure: true },
);
const currentLogs = kubectl(
[
'-n',
NAMESPACE,
'logs',
job.target.metadata.name,
'--container=management',
'--tail=80',
],
{ capture: true, quiet: true, allowFailure: true },
);
const previousLogs = kubectl(
[
'-n',
NAMESPACE,
'logs',
job.target.metadata.name,
'--container=management',
'--previous',
'--tail=80',
],
{ capture: true, quiet: true, allowFailure: true },
);
return Object.freeze({
targetPod: job.target.metadata.name,
expectedUid: job.target.metadata.uid,
state: state.status === 0 ? JSON.parse(state.stdout) : state.stderr,
currentLogs: currentLogs.stdout || currentLogs.stderr,
previousLogs: previousLogs.stdout || previousLogs.stderr,
});
};
if (expected.statusCode === 200) {
if (!completion.complete || terminated?.exitCode !== 0) {
throw new Error(
`${job.name} unexpectedly failed: ${JSON.stringify({
output,
target: targetFailureEvidence(),
})}`,
);
}
assert.equal(completion.complete, true);
assert.equal(terminated?.exitCode, 0);
assert.equal(output.event, 'command_completed');
assert.equal(output.result.operation, job.command.operation);
if (expected.resultStatus !== undefined) {
assert.equal(
Array.isArray(expected.resultStatus)
? expected.resultStatus.includes(output.result.status)
: output.result.status === expected.resultStatus,
true,
);
}
} else {
if (!completion.failed || terminated?.exitCode !== 1) {
throw new Error(
`${job.name} unexpectedly succeeded: ${JSON.stringify(output)}`,
);
}
assert.equal(completion.failed, true);
assert.equal(terminated?.exitCode, 1);
assert.equal(output.event, 'command_failed');
assert.equal(output.statusCode, expected.statusCode);
assert.equal(output.responseCode, expected.responseCode);
}
return Object.freeze({
name: job.name,
targetPod: job.target.metadata.name,
targetPodUid: job.target.metadata.uid,
targetNode: job.target.spec.nodeName,
statusCode: expected.statusCode,
transportAttempts: attemptFact.transportAttempts,
output,
});
};
const executeClient = async (definition, expected) => {
const job = createClientJob(definition);
return waitClientJob(job, expected);
};
const executeWave = async (definitions, expected) => {
const jobs = definitions.map(createClientJob);
return Promise.all(jobs.map((job) => waitClientJob(job, expected)));
};
const executeWithoutClientCertificate = async ({
target,
command,
bearer,
}) => {
const name = 'ql3-wcm-no-client-certificate';
const inputSecret = `${name}-input`;
const rawConfig = {
schemaVersion: 1,
endpoint: clientConfig.endpoint,
servername: clientConfig.servername,
caFile: '/var/run/qinglong3/client/ca.crt',
requestTimeoutMs: clientConfig.requestTimeoutMs,
};
create({
apiVersion: 'v1',
kind: 'Secret',
metadata: { name: inputSecret, namespace: NAMESPACE },
type: 'Opaque',
stringData: {
'client.json': `${JSON.stringify(rawConfig)}\n`,
'command.json': `${JSON.stringify(command)}\n`,
'assertion.jwt': bearer,
'ca.crt': caText,
},
});
create({
apiVersion: 'batch/v1',
kind: 'Job',
metadata: { name, namespace: NAMESPACE },
spec: {
backoffLimit: 0,
activeDeadlineSeconds: 60,
ttlSecondsAfterFinished: 600,
template: {
metadata: {
labels: {
app: 'ql3-worker-manager-live-no-client-certificate',
'qinglong.io/worker-credential-management-client': 'true',
},
},
spec: {
automountServiceAccountToken: false,
restartPolicy: 'Never',
hostAliases: [
{ ip: target.status.podIP, hostnames: [SERVERNAME] },
],
securityContext: {
runAsNonRoot: true,
runAsUser: 10001,
runAsGroup: 10001,
fsGroup: 10001,
seccompProfile: { type: 'RuntimeDefault' },
},
containers: [
{
name: 'client',
image: adminImage,
imagePullPolicy: 'Never',
command: [
'node',
'-e',
RAW_REQUEST_DIAGNOSTIC_SCRIPT,
'--',
'/var/run/qinglong3/client/client.json',
'/var/run/qinglong3/client/command.json',
'/var/run/qinglong3/client/assertion.jwt',
],
terminationMessagePolicy: 'File',
securityContext: {
allowPrivilegeEscalation: false,
readOnlyRootFilesystem: true,
capabilities: { drop: ['ALL'] },
},
resources: {
requests: { cpu: '5m', memory: '16Mi' },
limits: { cpu: '100m', memory: '64Mi' },
},
volumeMounts: [
{
name: 'client-input',
mountPath: '/var/run/qinglong3/client',
readOnly: true,
},
],
},
],
volumes: [
{
name: 'client-input',
secret: { secretName: inputSecret, defaultMode: 288 },
},
],
},
},
},
});
await waitFor(`${name} completion`, 90_000, () => {
const current = kubectlJson(['-n', NAMESPACE, 'get', 'job', name]);
const complete = current.status.conditions?.some(
(condition) =>
condition.type === 'Complete' && condition.status === 'True',
);
return complete
? { ready: true, value: current }
: { ready: false, fact: JSON.stringify(current.status ?? {}) };
});
const pods = kubectlJson([
'-n',
NAMESPACE,
'get',
'pods',
'-l',
`job-name=${name}`,
]).items;
assert.equal(pods.length, 1);
const terminated =
pods[0].status.containerStatuses?.[0]?.state?.terminated;
assert.equal(terminated?.exitCode, 0);
const output = JSON.parse(terminated?.message ?? '{}');
assert.equal(output.event, 'raw_request_completed');
assert.equal(output.statusCode, 401);
assert.equal(output.protocol, 'TLSv1.3');
assert.equal(output.body.error.code, 'client_certificate_required');
assert.equal(String(terminated?.message ?? '').includes(bearer), false);
return Object.freeze({
targetPod: target.metadata.name,
statusCode: output.statusCode,
responseCode: output.body.error.code,
});
};
const executeConnectivity = async (target, index) => {
const name = `ql3-wcm-initial-connectivity-${index + 1}`;
create({
apiVersion: 'batch/v1',
kind: 'Job',
metadata: { name, namespace: NAMESPACE },
spec: {
backoffLimit: 0,
activeDeadlineSeconds: 60,
ttlSecondsAfterFinished: 600,
template: {
metadata: {
labels: {
app: 'ql3-worker-manager-live-connectivity',
'qinglong.io/worker-credential-management-client': 'true',
},
},
spec: {
automountServiceAccountToken: false,
restartPolicy: 'Never',
hostAliases: [
{ ip: target.status.podIP, hostnames: [SERVERNAME] },
],
securityContext: {
runAsNonRoot: true,
runAsUser: 10001,
runAsGroup: 10001,
fsGroup: 10001,
seccompProfile: { type: 'RuntimeDefault' },
},
containers: [
{
name: 'connectivity',
image: adminImage,
imagePullPolicy: 'Never',
command: [
'node',
'-e',
CONNECTIVITY_SCRIPT,
'--',
SERVERNAME,
'8444',
],
terminationMessagePolicy: 'File',
securityContext: {
allowPrivilegeEscalation: false,
readOnlyRootFilesystem: true,
capabilities: { drop: ['ALL'] },
},
resources: {
requests: { cpu: '5m', memory: '16Mi' },
limits: { cpu: '100m', memory: '64Mi' },
},
volumeMounts: [
{
name: 'ca',
mountPath: '/var/run/qinglong3/ca',
readOnly: true,
},
],
},
],
volumes: [
{
name: 'ca',
secret: {
secretName: TLS_SECRET,
defaultMode: 292,
items: [{ key: 'ca.crt', path: 'ca.crt' }],
},
},
],
},
},
},
});
await waitFor(`${name} completion`, 90_000, () => {
const current = kubectlJson(['-n', NAMESPACE, 'get', 'job', name]);
const complete = current.status.conditions?.some(
(condition) =>
condition.type === 'Complete' && condition.status === 'True',
);
const failed = current.status.conditions?.some(
(condition) =>
condition.type === 'Failed' && condition.status === 'True',
);
return complete || failed
? { ready: true, value: { complete, failed } }
: { ready: false, fact: JSON.stringify(current.status ?? {}) };
});
const pods = kubectlJson([
'-n',
NAMESPACE,
'get',
'pods',
'-l',
`job-name=${name}`,
]).items;
assert.equal(pods.length, 1);
const terminated =
pods[0].status.containerStatuses?.[0]?.state?.terminated;
const output = JSON.parse(terminated?.message ?? '{}');
assert.equal(terminated?.exitCode, 0);
assert.equal(output.event, 'connectivity_ready');
assert.equal(output.statusCode, 200);
assert.equal(output.protocol, 'TLSv1.3');
return Object.freeze({
name,
targetPod: target.metadata.name,
targetNode: target.spec.nodeName,
sourceNode: pods[0].spec.nodeName,
attempts: output.attempts,
});
};
const initialConnectivity = await Promise.all(
generation1Pods.map(executeConnectivity),
);
const firstCommands = Array.from({ length: 8 }, (_, index) =>
planCommand(index + 1),
);
const inspectExistingPlan = (suffix) =>
inspectCommand(firstCommands[0].request.actionRef, suffix);
const noClientCertificate = await executeWithoutClientCertificate({
target: generation1Pods[0],
command: inspectExistingPlan('no-client-certificate'),
bearer: assertion(oldKey, 'operator-a', 'no-client-certificate'),
});
const firstWave = await executeWave(
firstCommands.map((command, index) => ({
name: `ql3-wcm-plan-admit-${String(index + 1).padStart(2, '0')}`,
target: generation1Pods[index % 2],
command,
bearer: assertion(oldKey, 'operator-a', `admit-${index + 1}`),
})),
{ statusCode: 200, resultStatus: ['created', 'existing'] },
);
const committedOperationName = 'ql3-worker-credential-management-client';
const committedOperationCommand = inspectExistingPlan(
'committed-operation',
);
const committedOperationAssertion = assertion(
oldKey,
'operator-a',
'committed-operation',
);
create(loadOperationManifest('service-account.yaml'));
create(loadOperationManifest('network-policy.yaml'));
create({
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: 'ql3-worker-credential-management-request',
namespace: NAMESPACE,
},
immutable: true,
data: {
'client.json': `${JSON.stringify(clientConfig)}\n`,
'command.json': `${JSON.stringify(committedOperationCommand)}\n`,
},
});
create({
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: 'ql3-worker-credential-management-client-trust',
namespace: NAMESPACE,
},
immutable: true,
data: { 'ca.crt': caText },
});
create({
apiVersion: 'v1',
kind: 'Secret',
metadata: {
name: 'ql3-worker-credential-management-assertion',
namespace: NAMESPACE,
},
immutable: true,
type: 'Opaque',
stringData: { 'assertion.jwt': committedOperationAssertion },
});
create({
apiVersion: 'v1',
kind: 'Secret',
metadata: {
name: 'ql3-worker-credential-management-client-identity',
namespace: NAMESPACE,
},
immutable: true,
type: 'kubernetes.io/tls',
stringData: {
'tls.crt': activeClientCertificateText,
'tls.key': activeClientKeyText,
},
});
const committedOperationJob = loadOperationManifest('job.yaml');
for (const candidate of [
...committedOperationJob.spec.template.spec.initContainers,
...committedOperationJob.spec.template.spec.containers,
]) {
candidate.image = adminImage;
candidate.imagePullPolicy = 'Never';
}
create(committedOperationJob);
const committedOperationCompletion = await waitFor(
'committed management client operation completion',
150_000,
() => {
const current = kubectlJson([
'-n',
NAMESPACE,
'get',
'job',
committedOperationName,
]);
const complete = current.status.conditions?.some(
(condition) =>
condition.type === 'Complete' && condition.status === 'True',
);
const failed = current.status.conditions?.some(
(condition) =>
condition.type === 'Failed' && condition.status === 'True',
);
return complete || failed
? { ready: true, value: { complete, failed, current } }
: { ready: false, fact: JSON.stringify(current.status ?? {}) };
},
);
const committedOperationPods = kubectlJson([
'-n',
NAMESPACE,
'get',
'pods',
'-l',
`job-name=${committedOperationName}`,
]).items;
assert.equal(committedOperationPods.length, 1);
const committedOperationPod = committedOperationPods[0];
const committedOperationInit =
committedOperationPod.status.initContainerStatuses?.[0];
const committedOperationClient =
committedOperationPod.status.containerStatuses?.[0];
assert.equal(committedOperationCompletion.complete, true);
assert.equal(committedOperationCompletion.failed, false);
assert.equal(committedOperationCompletion.current.status.succeeded, 1);
assert.equal(committedOperationInit?.state?.terminated?.exitCode, 0);
assert.equal(committedOperationInit?.restartCount, 0);
assert.equal(committedOperationClient?.state?.terminated?.exitCode, 0);
assert.equal(committedOperationClient?.restartCount, 0);
assert.equal(
committedOperationPod.spec.automountServiceAccountToken,
false,
);
assert.equal(committedOperationPod.spec.enableServiceLinks, false);
assert.equal(
committedOperationPod.spec.serviceAccountName,
committedOperationName,
);
assert.equal(
committedOperationPod.spec.volumes.some((volume) =>
volume.projected?.sources?.some(
(source) => source.serviceAccountToken !== undefined,
),
),
false,
);
assert.equal(
committedOperationPod.metadata.labels?.[
'qinglong.io/worker-credential-management-client'
],
'true',
);
assert.equal(
String(
committedOperationClient?.state?.terminated?.message ?? '',
).includes(committedOperationAssertion),
false,
);
assert.equal(
String(
committedOperationClient?.state?.terminated?.message ?? '',
).includes(POSTGRES_ROLES.workerCredentialManager.password),
false,
);
for (const [kind, resourceName] of [
['configmap', 'ql3-worker-credential-management-request'],
['configmap', 'ql3-worker-credential-management-client-trust'],
['secret', 'ql3-worker-credential-management-assertion'],
['secret', 'ql3-worker-credential-management-client-identity'],
]) {
assert.equal(
kubectlJson(['-n', NAMESPACE, 'get', kind, resourceName]).immutable,
true,
);
}
const committedClientOperation = Object.freeze({
job: committedOperationName,
source: 'repository base manifests with test-only image substitution',
initExitCode: committedOperationInit.state.terminated.exitCode,
clientExitCode: committedOperationClient.state.terminated.exitCode,
clientRestarts: committedOperationClient.restartCount,
operation: committedOperationCommand.operation,
completed: committedOperationCompletion.complete,
assertionInTerminationMessage: false,
serviceAccountTokenMounted: false,
immutableInputs: true,
});
const limitedWave = await executeWave(
Array.from({ length: 8 }, (_, index) => ({
name: `ql3-wcm-plan-limit-${String(index + 1).padStart(2, '0')}`,
target: generation1Pods[index % 2],
command: planCommand(index + 9),
bearer: assertion(oldKey, 'operator-a', `limit-${index + 1}`),
})),
{ statusCode: 429, responseCode: 'quota_exceeded' },
);
const replay = await executeClient(
{
name: 'ql3-wcm-plan-replay-cross-pod',
target: generation1Pods[1],
command: firstCommands[0],
bearer: assertion(oldKey, 'operator-a', 'replay-cross-pod'),
},
{ statusCode: 200, resultStatus: 'existing' },
);
run(
'openssl',
['ca', '-batch', '-config', caConfig, '-revoke', clientOldCertificate],
{ capture: true, quiet: true },
);
run(
'openssl',
[
'ca',
'-gencrl',
'-config',
caConfig,
'-out',
clientCertificateRevocationList,
],
{ capture: true, quiet: true },
);
clientCrlText = fs.readFileSync(clientCertificateRevocationList, 'utf8');
apply({
apiVersion: 'v1',
kind: 'Secret',
metadata: { name: TLS_SECRET, namespace: NAMESPACE },
type: 'kubernetes.io/tls',
stringData: {
'tls.crt': tlsCertificateText,
'tls.key': tlsKeyText,
'ca.crt': caText,
'client.crl': clientCrlText,
},
});
const preRevocationUids = new Set(
generation1Pods.map((pod) => pod.metadata.uid),
);
patchGeneration('client-crl-2');
waitRollout();
const clientRevocationPods = await readyManagerPods(preRevocationUids);
const revokedClientCertificate = await executeClient(
{
name: 'ql3-wcm-revoked-client-certificate',
target: clientRevocationPods[0],
command: inspectExistingPlan('revoked-client-certificate'),
bearer: assertion(oldKey, 'operator-a', 'revoked-client-certificate'),
clientCertificateText: clientOldCertificateText,
clientKeyText: clientOldKeyText,
},
{ statusCode: 401, responseCode: 'client_certificate_required' },
);
activeClientCertificateText = clientNewCertificateText;
activeClientKeyText = clientNewKeyText;
const activeClientCertificate = await executeClient(
{
name: 'ql3-wcm-active-client-certificate',
target: clientRevocationPods[1],
command: inspectExistingPlan('active-client-certificate'),
bearer: assertion(oldKey, 'operator-a', 'active-client-certificate'),
},
{ statusCode: 200 },
);
const generation1Uids = new Set(
clientRevocationPods.map((pod) => pod.metadata.uid),
);
applyIdentity(keyset2);
patchGeneration('2');
waitRollout();
const generation2Pods = await readyManagerPods(generation1Uids);
const overlapOld = await executeClient(
{
name: 'ql3-wcm-overlap-old-key',
target: generation2Pods[0],
command: inspectExistingPlan('overlap-old'),
bearer: assertion(oldKey, 'operator-a', 'overlap-old'),
},
{ statusCode: 200 },
);
const overlapNew = await executeClient(
{
name: 'ql3-wcm-overlap-new-key',
target: generation2Pods[1],
command: inspectExistingPlan('overlap-new'),
bearer: assertion(newKey, 'operator-a', 'overlap-new'),
},
{ statusCode: 200 },
);
const generation2Uids = new Set(
generation2Pods.map((pod) => pod.metadata.uid),
);
applyIdentity(keyset3);
patchGeneration('3');
waitRollout();
const generation3Pods = await readyManagerPods(generation2Uids);
const revokedOld = await executeClient(
{
name: 'ql3-wcm-revoked-old-key',
target: generation3Pods[0],
command: inspectExistingPlan('revoked-old'),
bearer: assertion(oldKey, 'operator-a', 'revoked-old'),
},
{ statusCode: 401, responseCode: 'authentication_required' },
);
const activeNew = await executeClient(
{
name: 'ql3-wcm-active-new-key',
target: generation3Pods[1],
command: inspectExistingPlan('active-new'),
bearer: assertion(newKey, 'operator-a', 'active-new'),
},
{ statusCode: 200 },
);
applyIdentity(keyset2);
patchGeneration('rollback-2');
const rollbackPod = await waitFor(
'rollback surge Pod fail-closed',
120_000,
() => {
const deployment = kubectlJson([
'-n',
NAMESPACE,
'get',
'deployment',
DEPLOYMENT,
]);
const pods = kubectlJson([
'-n',
NAMESPACE,
'get',
'pods',
'-l',
`app.kubernetes.io/name=${DEPLOYMENT}`,
]).items.filter((pod) => pod.metadata.deletionTimestamp === undefined);
const ready = pods.filter((pod) =>
pod.status.conditions?.some(
(condition) =>
condition.type === 'Ready' && condition.status === 'True',
),
);
const candidate = pods.find(
(pod) =>
!generation3Pods.some(
(current) => current.metadata.uid === pod.metadata.uid,
) &&
pod.status.containerStatuses?.[0] &&
!pod.status.containerStatuses[0].ready &&
(pod.status.containerStatuses[0].restartCount > 0 ||
pod.status.containerStatuses[0].state?.waiting?.reason ===
'CrashLoopBackOff'),
);
return ready.length === 2 &&
deployment.status.readyReplicas === 2 &&
candidate
? { ready: true, value: candidate }
: {
ready: false,
fact:
`ready=${ready.length} replicas=${
deployment.status.readyReplicas ?? 0
} ` + `pods=${pods.length}`,
};
},
);
assert.equal(
rollbackPod.status.containerStatuses[0].lastState?.terminated?.exitCode ??
rollbackPod.status.containerStatuses[0].state?.terminated?.exitCode,
1,
);
applyIdentity(keyset3);
patchGeneration('3-recovered');
kubectl(
[
'-n',
NAMESPACE,
'delete',
'pod',
rollbackPod.metadata.name,
'--grace-period=0',
'--force',
'--wait=true',
],
{ capture: true, quiet: true },
);
waitRollout();
const recoveredIdentityPods = await readyManagerPods();
const recoveredIdentity = await Promise.all(
recoveredIdentityPods.map((pod, index) =>
executeClient(
{
name: `ql3-wcm-identity-recovered-${index + 1}`,
target: pod,
command: inspectExistingPlan(`identity-recovered-${index + 1}`),
bearer: assertion(
newKey,
'operator-a',
`identity-recovered-${index + 1}`,
),
},
{ statusCode: 200 },
),
),
);
const beforeFailureFacts = (
await migrationDatabase.pool.query(
`SELECT
(SELECT count(*)::integer
FROM "ql3"."worker_credential_management_plans") AS "plans",
(SELECT consumed_count::integer
FROM "ql3"."worker_credential_management_quota_buckets"
WHERE project_id = 'cluster-authority'
AND subject_type = 'user'
AND subject_id = 'operator-a'
AND operation = 'worker-credential.plan') AS "consumedCount",
(SELECT jsonb_array_length(receipt_ids)::integer
FROM "ql3"."worker_credential_management_quota_buckets"
WHERE project_id = 'cluster-authority'
AND subject_type = 'user'
AND subject_id = 'operator-a'
AND operation = 'worker-credential.plan') AS "receiptCount",
(SELECT generation::integer
FROM "ql3"."plugin_package_identity_keyset_ledger"
WHERE authority = 'worker-credential-management') AS "identityGeneration"`,
)
).rows[0];
assert.deepEqual(beforeFailureFacts, {
plans: 8,
consumedCount: 8,
receiptCount: 8,
identityGeneration: 3,
});
await migrationDatabase.close();
migrationDatabase = undefined;
const createHealthJob = ({ name, target }) => {
create({
apiVersion: 'batch/v1',
kind: 'Job',
metadata: { name, namespace: NAMESPACE },
spec: {
backoffLimit: 0,
activeDeadlineSeconds: 60,
ttlSecondsAfterFinished: 600,
template: {
metadata: {
labels: {
app: 'ql3-worker-manager-live-health',
'qinglong.io/worker-credential-management-client': 'true',
},
},
spec: {
automountServiceAccountToken: false,
restartPolicy: 'Never',
hostAliases: [
{ ip: target.status.podIP, hostnames: [SERVERNAME] },
],
securityContext: {
runAsNonRoot: true,
runAsUser: 10001,
runAsGroup: 10001,
fsGroup: 10001,
seccompProfile: { type: 'RuntimeDefault' },
},
containers: [
{
name: 'health',
image: adminImage,
imagePullPolicy: 'Never',
command: [
'node',
'-e',
HEALTH_SCRIPT,
'--',
SERVERNAME,
'8444',
],
terminationMessagePolicy: 'File',
securityContext: {
allowPrivilegeEscalation: false,
readOnlyRootFilesystem: true,
capabilities: { drop: ['ALL'] },
},
resources: {
requests: { cpu: '5m', memory: '16Mi' },
limits: { cpu: '100m', memory: '64Mi' },
},
volumeMounts: [
{
name: 'ca',
mountPath: '/var/run/qinglong3/ca',
readOnly: true,
},
],
},
],
volumes: [
{
name: 'ca',
secret: {
secretName: TLS_SECRET,
defaultMode: 292,
items: [{ key: 'ca.crt', path: 'ca.crt' }],
},
},
],
},
},
},
});
return Object.freeze({ name, target });
};
const waitHealthJob = async (job) => {
await waitFor(`${job.name} completion`, 90_000, () => {
const current = kubectlJson(['-n', NAMESPACE, 'get', 'job', job.name]);
const complete = current.status.conditions?.some(
(condition) =>
condition.type === 'Complete' && condition.status === 'True',
);
const failed = current.status.conditions?.some(
(condition) =>
condition.type === 'Failed' && condition.status === 'True',
);
return complete || failed
? { ready: true, value: current }
: { ready: false, fact: JSON.stringify(current.status ?? {}) };
});
const pods = kubectlJson([
'-n',
NAMESPACE,
'get',
'pods',
'-l',
`job-name=${job.name}`,
]).items;
assert.equal(pods.length, 1);
const terminated =
pods[0].status.containerStatuses?.[0]?.state?.terminated;
assert.equal(terminated?.exitCode, 0);
const output = JSON.parse(
(terminated.message ?? '').split('\n').filter(Boolean).at(-1),
);
assert.deepEqual(
output.responses.map((entry) => entry.protocol),
['TLSv1.3', 'TLSv1.3'],
);
return Object.freeze({
targetPod: job.target.metadata.name,
readyStatus: output.responses.find((entry) => entry.path === '/readyz')
.statusCode,
liveStatus: output.responses.find((entry) => entry.path === '/livez')
.statusCode,
});
};
const healthWave = async (label, pods) => {
const jobs = pods.map((pod, index) =>
createHealthJob({
name: `ql3-wcm-health-${label}-${index + 1}`,
target: pod,
}),
);
return Promise.all(jobs.map(waitHealthJob));
};
run(docker, ['stop', '--time', '1', postgres], {
capture: true,
quiet: true,
});
const unavailableRequests = await Promise.all(
recoveredIdentityPods.map((pod, index) =>
executeClient(
{
name: `ql3-wcm-postgres-down-${index + 1}`,
target: pod,
command: inspectExistingPlan(`postgres-down-${index + 1}`),
bearer: assertion(
newKey,
'operator-a',
`postgres-down-${index + 1}`,
),
},
{ statusCode: 503, responseCode: 'unavailable' },
),
),
);
const withdrawnHealth = await healthWave(
'withdrawn',
recoveredIdentityPods,
);
assert.deepEqual(
withdrawnHealth.map((entry) => entry.readyStatus),
[503, 503],
);
assert.deepEqual(
withdrawnHealth.map((entry) => entry.liveStatus),
[200, 200],
);
await waitFor(
'manager readiness withdrawn from both replicas',
30_000,
() => {
const current = kubectlJson([
'-n',
NAMESPACE,
'get',
'deployment',
DEPLOYMENT,
]);
return (current.status.readyReplicas ?? 0) === 0
? { ready: true, value: current }
: {
ready: false,
fact: `${
current.status.readyReplicas ?? 0
} replicas remain Ready`,
};
},
);
run(docker, ['start', postgres], { capture: true, quiet: true });
await waitFor('restarted PostgreSQL readiness', 60_000, () => {
const result = run(
docker,
[
'exec',
postgres,
'pg_isready',
'-h',
'127.0.0.1',
'-U',
'postgres',
'-d',
POSTGRES_DATABASE,
],
{ capture: true, quiet: true, allowFailure: true },
);
return result.status === 0
? { ready: true, value: true }
: { ready: false, fact: result.stderr || result.stdout };
});
const stillWithdrawnHealth = await healthWave(
'still-withdrawn',
recoveredIdentityPods,
);
assert.deepEqual(
stillWithdrawnHealth.map((entry) => entry.readyStatus),
[503, 503],
);
const withdrawnUids = new Set(
recoveredIdentityPods.map((pod) => pod.metadata.uid),
);
patchGeneration('3-fresh-postgres');
waitRollout();
const freshPods = await readyManagerPods(withdrawnUids);
const freshRequests = await Promise.all(
freshPods.map((pod, index) =>
executeClient(
{
name: `ql3-wcm-postgres-fresh-${index + 1}`,
target: pod,
command: inspectExistingPlan(`postgres-fresh-${index + 1}`),
bearer: assertion(
newKey,
'operator-a',
`postgres-fresh-${index + 1}`,
),
},
{ statusCode: 200 },
),
),
);
const finalFacts = JSON.parse(
run(
docker,
[
'exec',
'-e',
`PGPASSWORD=${POSTGRES_ROLES.workerCredentialManager.password}`,
postgres,
'psql',
'-U',
POSTGRES_ROLES.workerCredentialManager.user,
'-d',
POSTGRES_DATABASE,
'--tuples-only',
'--no-align',
'--command',
`SELECT json_build_object(
'plans', (SELECT count(*)::integer
FROM "ql3"."worker_credential_management_plans"),
'consumedCount', (SELECT consumed_count::integer
FROM "ql3"."worker_credential_management_quota_buckets"
WHERE project_id = 'cluster-authority'
AND subject_type = 'user'
AND subject_id = 'operator-a'
AND operation = 'worker-credential.plan'),
'receiptCount', (SELECT jsonb_array_length(receipt_ids)::integer
FROM "ql3"."worker_credential_management_quota_buckets"
WHERE project_id = 'cluster-authority'
AND subject_type = 'user'
AND subject_id = 'operator-a'
AND operation = 'worker-credential.plan'),
'identityGeneration', (SELECT generation::integer
FROM "ql3"."plugin_package_identity_keyset_ledger"
WHERE authority = 'worker-credential-management'),
'activeKeyIds', (SELECT active_key_ids
FROM "ql3"."plugin_package_identity_keyset_ledger"
WHERE authority = 'worker-credential-management'),
'revokedKeyIds', (SELECT revoked_key_ids
FROM "ql3"."plugin_package_identity_keyset_ledger"
WHERE authority = 'worker-credential-management'))`,
],
{ capture: true, quiet: true },
).stdout,
);
assert.deepEqual(finalFacts, {
plans: 8,
consumedCount: 8,
receiptCount: 8,
identityGeneration: 3,
activeKeyIds: [newKey.kid],
revokedKeyIds: [oldKey.kid],
});
const report = {
schemaVersion: 1,
fixture:
'qinglong/worker-credential-management-kubernetes-live-contract@v1',
kubernetes: {
distribution: 'k3s',
image: K3S_IMAGE,
imageDigest: K3S_DIGEST,
architecture: k3sImage.Architecture,
serverVersion: kubectlJson(['version']).serverVersion.gitVersion,
nodes: kubectlJson(['get', 'nodes']).items.map((node) => ({
name: node.metadata.name,
uid: node.metadata.uid,
})),
},
postgresql: {
image: POSTGRES_IMAGE,
imageId: postgresImage.Id,
architecture: postgresImage.Architecture,
database: POSTGRES_DATABASE,
managerRole: POSTGRES_ROLES.workerCredentialManager.user,
maxConnectionsPerPod: 2,
},
deployment: {
replicas: 2,
requiredPodAntiAffinity: true,
initialConnectivityChecks: initialConnectivity.length,
generation1Pods: generation1Pods.map((pod) => ({
name: pod.metadata.name,
uid: pod.metadata.uid,
node: pod.spec.nodeName,
})),
generation2Pods: generation2Pods.map((pod) => ({
name: pod.metadata.name,
uid: pod.metadata.uid,
node: pod.spec.nodeName,
})),
generation3Pods: generation3Pods.map((pod) => ({
name: pod.metadata.name,
uid: pod.metadata.uid,
node: pod.spec.nodeName,
})),
freshPostgresPods: freshPods.map((pod) => ({
name: pod.metadata.name,
uid: pod.metadata.uid,
node: pod.spec.nodeName,
})),
serviceAccountAutomount: false,
projectedServiceAccountToken: false,
exactPostgresEgressCidr: `${postgresAddress}/32`,
},
managementClientOperation: committedClientOperation,
clientCertificateAuthentication: {
healthWithoutClientCertificate: initialConnectivity.length === 2,
businessRouteWithoutClientCertificateStatus:
noClientCertificate.statusCode,
businessRouteWithoutClientCertificateCode:
noClientCertificate.responseCode,
revokedCertificateStatus: revokedClientCertificate.statusCode,
activeCertificateStatus: activeClientCertificate.statusCode,
rotationRequiresServerRollout: true,
},
quota: {
competingPods: 2,
admitted: firstWave.length,
limited: limitedWave.length,
requestsPerPodPerWave: 4,
crossPodReplayStatus: replay.output.result.status,
durablePlans: finalFacts.plans,
durableConsumedCount: finalFacts.consumedCount,
durableReceiptCount: finalFacts.receiptCount,
},
identity: {
generations: [1, 2, 3],
overlapOldAccepted: overlapOld.statusCode === 200,
overlapNewAccepted: overlapNew.statusCode === 200,
revokedOldRejected: revokedOld.statusCode === 401,
activeNewAccepted: activeNew.statusCode === 200,
rollbackPod: {
name: rollbackPod.metadata.name,
uid: rollbackPod.metadata.uid,
exitCode: 1,
},
readyReplicasDuringRollback: 2,
recoveredReplicas: recoveredIdentity.length,
durableGeneration: finalFacts.identityGeneration,
activeKeyIds: finalFacts.activeKeyIds,
revokedKeyIds: finalFacts.revokedKeyIds,
},
availability: {
unavailableResponses: unavailableRequests.map(
(entry) => entry.statusCode,
),
withdrawnReadyStatuses: withdrawnHealth.map(
(entry) => entry.readyStatus,
),
withdrawnLiveStatuses: withdrawnHealth.map((entry) => entry.liveStatus),
stillWithdrawnAfterDatabaseRestart: stillWithdrawnHealth.every(
(entry) => entry.readyStatus === 503,
),
freshActivationResponses: freshRequests.map(
(entry) => entry.statusCode,
),
},
gates: {
realThreeNodeKubernetes: true,
twoManagerPodsOnDistinctNodes: true,
tls13ProductionClientAcrossBothPods: true,
committedOneShotClientOperation: true,
clientCertificateAndOidcRequiredForBusinessRoutes: true,
revokedClientCertificateRejectedAfterRollout: true,
durableQuotaConvergedAcrossPods: true,
crossPodReplayConsumedNoAdditionalQuota: true,
identityProjectionOverlapAndRevocation: true,
rollbackSurgeFailedClosedWithoutAvailabilityLoss: true,
databaseFailureWithdrewReadinessButNotLiveness: true,
availabilityFenceDidNotRecoverInPlace: true,
freshActivationRecoveredBothReplicas: true,
passed: true,
},
limitations: [
'three privileged K3s Docker nodes are not production infrastructure or control-plane HA evidence',
'PostgreSQL transport is deliberately plaintext only inside the isolated disposable live fixture',
'database failure uses container stop rather than a raw-wire or CNI partition',
'identity assertions use deterministic local strong-User ceremony rather than an external IdP',
],
};
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
} finally {
if (migrationDatabase) {
await migrationDatabase.close().catch(() => undefined);
}
for (const name of [...createdContainers].reverse()) {
run(docker, ['rm', '-f', '-v', name], {
capture: true,
quiet: true,
allowFailure: true,
});
}
if (networkCreated) {
run(docker, ['network', 'rm', network], {
capture: true,
quiet: true,
allowFailure: true,
});
}
if (adminImageBuilt) {
run(docker, ['image', 'rm', '-f', adminImage], {
capture: true,
quiet: true,
allowFailure: true,
});
}
fs.rmSync(temporary, { recursive: true, force: true });
}
}
main().catch((error) => {
process.stderr.write(
`ql3 Worker credential manager Kubernetes live contract failed: ` +
`${error.stack || error}\n`,
);
process.exitCode = 1;
});