Files
qinglong/scripts/ql3-vault-kv-worker-secret-deployment-audit.cjs
T

189 lines
6.6 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const fs = require('node:fs');
const path = require('node:path');
const yaml = require('js-yaml');
function finding(code, detail) {
return Object.freeze({ code, detail });
}
function load(readFile, root, relativePath) {
return yaml.load(readFile(path.join(root, relativePath), 'utf8'));
}
function loadDocuments(source) {
const documents = [];
yaml.loadAll(source, (document) => {
if (document) documents.push(document);
});
return documents;
}
function named(values, name) {
return (values ?? []).find((value) => value?.name === name);
}
function auditVaultKvWorkerSecretDeployment(options = {}) {
const root = path.resolve(options.root ?? path.join(__dirname, '..'));
const readFile = options.readFile ?? fs.readFileSync;
const findings = [];
try {
const directory = 'deploy/kubernetes/ql3-cluster/vault-kv-worker-secret';
const kustomization = load(
readFile,
root,
`${directory}/kustomization.yaml`,
);
const patch = load(readFile, root, `${directory}/deployment-patch.yaml`);
const credentials = loadDocuments(
readFile(path.join(root, directory, 'credentials.example.yaml'), 'utf8'),
);
const readme = readFile(path.join(root, directory, 'README.md'), 'utf8');
if (
kustomization?.apiVersion !== 'kustomize.config.k8s.io/v1beta1' ||
kustomization?.kind !== 'Kustomization' ||
JSON.stringify(kustomization?.resources) !==
JSON.stringify(['../base']) ||
JSON.stringify(kustomization?.patches) !==
JSON.stringify([{ path: 'deployment-patch.yaml' }])
) {
findings.push(
finding(
'QL3_VAULT_KV_WORKER_SECRET_KUSTOMIZATION_INVALID',
'the Vault overlay must patch only the reviewed Cluster base',
),
);
}
const pod = patch?.spec?.template?.spec;
const container = named(pod?.containers, 'cluster-control');
const env = new Map(
(container?.env ?? []).map((entry) => [entry.name, entry]),
);
const expectedEnvironment = new Map([
['QL3_WORKER_SECRET_PROVIDER', 'vault-kv-v2'],
[
'QL3_WORKER_SECRET_VAULT_ENDPOINT',
'https://vault.vault.svc.cluster.local:8200',
],
[
'QL3_WORKER_SECRET_VAULT_CA_FILE',
'/var/run/secrets/qinglong3/worker-vault-trust/ca.pem',
],
[
'QL3_WORKER_SECRET_VAULT_TOKEN_FILE',
'/var/run/secrets/qinglong3/worker-vault-auth/token',
],
['QL3_WORKER_SECRET_VAULT_KV_MOUNT', 'worker-secrets'],
['QL3_WORKER_SECRET_VAULT_PATH_PREFIX', 'values/production'],
['QL3_WORKER_SECRET_VAULT_EXPECTED_POLICY', 'ql3-worker-secret-read'],
['QL3_WORKER_SECRET_VAULT_MAX_TOKEN_TTL_SECONDS', '900'],
['QL3_WORKER_SECRET_VAULT_REQUEST_TIMEOUT_MS', '5000'],
['QL3_WORKER_SECRET_VAULT_MAX_CONCURRENCY', '4'],
]);
if (
env.size !== expectedEnvironment.size + 1 ||
env.get('QL3_WORKER_SECRET_ROOT_DIRECTORY')?.$patch !== 'delete' ||
[...expectedEnvironment].some(
([name, value]) => env.get(name)?.value !== value,
)
) {
findings.push(
finding(
'QL3_VAULT_KV_WORKER_SECRET_ENVIRONMENT_INVALID',
'the overlay must select exact Vault KV v2 authority and bounds while deleting the mounted-value root',
),
);
}
const mounts = container?.volumeMounts ?? [];
const valuesMount = named(mounts, 'worker-secret-values');
const trustMount = named(mounts, 'worker-vault-trust');
const authMount = named(mounts, 'worker-vault-auth');
const volumes = pod?.volumes ?? [];
const valuesVolume = named(volumes, 'worker-secret-values');
const trustVolume = named(volumes, 'worker-vault-trust');
const authVolume = named(volumes, 'worker-vault-auth');
if (
mounts.length !== 3 ||
valuesMount?.$patch !== 'delete' ||
trustMount?.mountPath !==
'/var/run/secrets/qinglong3/worker-vault-trust' ||
trustMount?.readOnly !== true ||
authMount?.mountPath !== '/var/run/secrets/qinglong3/worker-vault-auth' ||
authMount?.readOnly !== true ||
volumes.length !== 3 ||
valuesVolume?.$patch !== 'delete' ||
trustVolume?.secret?.secretName !== 'ql3-cluster-worker-vault-trust' ||
trustVolume?.secret?.defaultMode !== 0o444 ||
JSON.stringify(trustVolume?.secret?.items) !==
JSON.stringify([{ key: 'ca.pem', path: 'ca.pem' }]) ||
authVolume?.secret?.secretName !== 'ql3-cluster-worker-vault-auth' ||
authVolume?.secret?.defaultMode !== 0o440 ||
JSON.stringify(authVolume?.secret?.items) !==
JSON.stringify([{ key: 'token', path: 'token' }])
) {
findings.push(
finding(
'QL3_VAULT_KV_WORKER_SECRET_PROJECTION_INVALID',
'only read-only Vault trust and short-lived auth projections are allowed; the value projection must be deleted',
),
);
}
if (
credentials.length !== 2 ||
credentials[0]?.kind !== 'Secret' ||
credentials[0]?.metadata?.name !== 'ql3-cluster-worker-vault-trust' ||
credentials[0]?.stringData?.['ca.pem'] !==
'REPLACE_WITH_PRIVATE_VAULT_CA_PEM' ||
credentials[1]?.kind !== 'Secret' ||
credentials[1]?.metadata?.name !== 'ql3-cluster-worker-vault-auth' ||
credentials[1]?.stringData?.token !==
'REPLACE_WITH_SHORT_LIVED_ORPHAN_VAULT_TOKEN'
) {
findings.push(
finding(
'QL3_VAULT_KV_WORKER_SECRET_EXAMPLE_INVALID',
'credential examples must contain only explicit non-production placeholders',
),
);
}
if (
!readme.includes('auth/token/lookup-self') ||
!readme.includes('capabilities = ["read"]') ||
!/must not project\s+the actual Worker Secret values/.test(readme) ||
!readme.includes('default-deny')
) {
findings.push(
finding(
'QL3_VAULT_KV_WORKER_SECRET_OPERATIONS_INVALID',
'operations guidance must preserve exact policy, direct custody and explicit egress boundaries',
),
);
}
} catch (error) {
findings.push(
finding(
'QL3_VAULT_KV_WORKER_SECRET_DEPLOYMENT_AUDIT_UNAVAILABLE',
error instanceof Error ? error.message : String(error),
),
);
}
return Object.freeze({
schemaVersion: 1,
provider: 'vault-kv-v2',
mountedValueProjection: false,
findings: Object.freeze(findings),
compatible: findings.length === 0,
});
}
if (require.main === module) {
const report = auditVaultKvWorkerSecretDeployment();
process.stdout.write(`${JSON.stringify(report)}\n`);
if (!report.compatible) process.exitCode = 1;
}
module.exports = { auditVaultKvWorkerSecretDeployment };