mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
189 lines
6.6 KiB
JavaScript
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 };
|