const fs = require('node:fs'); const path = require('node:path'); const yaml = require('js-yaml'); const OPERATION = path.join( 'deploy', 'kubernetes', 'ql3-cluster', 'operations', 'prompt-output-external-recovery', ); const NAME = 'ql3-prompt-output-external-recovery-verifier'; const NAMESPACE = 'qinglong3-recovery'; const WORKSPACE = '/var/run/qinglong3/prompt-output-external-recovery'; function finding(findings, code, message) { findings.push({ code, message }); } function load(root, relativePath, readFile) { return yaml.load(readFile(path.join(root, relativePath), 'utf8')); } function exactKeys(value, expected) { if (!value || typeof value !== 'object' || Array.isArray(value)) return false; const keys = Object.keys(value).sort(); const canonical = [...expected].sort(); return ( keys.length === canonical.length && keys.every((key, index) => key === canonical[index]) ); } function canonicalJson(root, relativePath, readFile) { return JSON.parse(readFile(path.join(root, relativePath), 'utf8')); } function auditPromptOutputExternalRecoveryDeployment(options = {}) { const root = options.root ?? path.resolve(__dirname, '..'); const readFile = options.readFile ?? fs.readFileSync; const findings = []; let namespace; let serviceAccount; let networkPolicy; let job; let kustomization; let command; try { namespace = load( root, path.join(OPERATION, 'base/namespace.yaml'), readFile, ); serviceAccount = load( root, path.join(OPERATION, 'base/service-account.yaml'), readFile, ); networkPolicy = load( root, path.join(OPERATION, 'base/network-policy.yaml'), readFile, ); job = load(root, path.join(OPERATION, 'base/job.yaml'), readFile); kustomization = load( root, path.join(OPERATION, 'base/kustomization.yaml'), readFile, ); command = canonicalJson( root, path.join(OPERATION, 'command.example.json'), readFile, ); } catch { finding(findings, 'QL3_RECOVERY_DEPLOYMENT_PARSE', 'resources must parse'); return { schemaVersion: 1, compatible: false, findings }; } if ( namespace?.apiVersion !== 'v1' || namespace?.kind !== 'Namespace' || namespace?.metadata?.name !== NAMESPACE || namespace?.metadata?.labels?.['qinglong.io/security-domain'] !== 'isolated-recovery' ) { finding( findings, 'QL3_RECOVERY_NAMESPACE', 'recovery must use its exact isolated namespace', ); } if ( serviceAccount?.apiVersion !== 'v1' || serviceAccount?.kind !== 'ServiceAccount' || serviceAccount?.metadata?.name !== NAME || serviceAccount?.metadata?.namespace !== NAMESPACE || serviceAccount?.automountServiceAccountToken !== false || serviceAccount?.secrets !== undefined ) { finding( findings, 'QL3_RECOVERY_SERVICE_ACCOUNT', 'verifier ServiceAccount must remain tokenless', ); } const podLabels = job?.spec?.template?.metadata?.labels; const pod = job?.spec?.template?.spec; const containers = pod?.containers; const container = Array.isArray(containers) ? containers[0] : undefined; const mounts = container?.volumeMounts; const volumes = pod?.volumes; if ( job?.apiVersion !== 'batch/v1' || job?.kind !== 'Job' || job?.metadata?.name !== NAME || job?.metadata?.namespace !== NAMESPACE || job?.spec?.backoffLimit !== 0 || job?.spec?.activeDeadlineSeconds !== 120 || job?.spec?.ttlSecondsAfterFinished !== 600 || podLabels?.['app.kubernetes.io/name'] !== NAME || podLabels?.['qinglong.io/execution-model'] !== 'caller-driven' || pod?.serviceAccountName !== NAME || pod?.automountServiceAccountToken !== false || pod?.enableServiceLinks !== false || pod?.restartPolicy !== 'Never' || pod?.initContainers !== undefined || !Array.isArray(containers) || containers.length !== 1 || container?.name !== 'verifier' || container?.image !== 'qinglong3-cluster-admin:3.0.0-alpha.0' || JSON.stringify(container?.command) !== JSON.stringify([ 'node', '/opt/qinglong/node_modules/@qinglong/cluster-admin/dist/prompt-output/external-recovery/promptOutputExternalRecoveryCli.js', ]) || JSON.stringify(container?.args) !== JSON.stringify(['run', '--command-file', `${WORKSPACE}/command.json`]) || container?.env !== undefined || container?.envFrom !== undefined || container?.ports !== undefined || container?.securityContext?.allowPrivilegeEscalation !== false || container?.securityContext?.readOnlyRootFilesystem !== true || JSON.stringify(container?.securityContext?.capabilities?.drop) !== JSON.stringify(['ALL']) || !Array.isArray(mounts) || mounts.length !== 1 || mounts[0]?.name !== 'recovery-workspace' || mounts[0]?.mountPath !== WORKSPACE || mounts[0]?.readOnly !== true || !Array.isArray(volumes) || volumes.length !== 1 || volumes[0]?.name !== 'recovery-workspace' || volumes[0]?.persistentVolumeClaim?.claimName !== 'ql3-prompt-output-external-recovery-workspace' || volumes[0]?.persistentVolumeClaim?.readOnly !== true ) { finding( findings, 'QL3_RECOVERY_JOB', 'verifier Job authority or resource envelope drifted', ); } if ( networkPolicy?.apiVersion !== 'networking.k8s.io/v1' || networkPolicy?.kind !== 'NetworkPolicy' || networkPolicy?.metadata?.namespace !== NAMESPACE || networkPolicy?.spec?.podSelector?.matchLabels?.[ 'app.kubernetes.io/name' ] !== NAME || JSON.stringify(networkPolicy?.spec?.policyTypes) !== JSON.stringify(['Ingress', 'Egress']) || !Array.isArray(networkPolicy?.spec?.ingress) || networkPolicy.spec.ingress.length !== 0 || !Array.isArray(networkPolicy?.spec?.egress) || networkPolicy.spec.egress.length !== 0 ) { finding( findings, 'QL3_RECOVERY_NETWORK_POLICY', 'verifier network must remain deny-all', ); } if ( kustomization?.apiVersion !== 'kustomize.config.k8s.io/v1beta1' || kustomization?.kind !== 'Kustomization' || JSON.stringify(kustomization?.resources) !== JSON.stringify([ 'namespace.yaml', 'service-account.yaml', 'network-policy.yaml', 'job.yaml', ]) ) { finding( findings, 'QL3_RECOVERY_KUSTOMIZATION', 'recovery base must contain no RBAC or credential resource', ); } const expectedCommandKeys = [ 'approverPublicKeyFiles', 'artifactFile', 'authorizationFile', 'custodyBundleFile', 'custodyPublicKeyFile', 'durableKeyFactFile', 'operation', 'recoveredMaterialFile', 'schemaVersion', ]; const fileFields = expectedCommandKeys.filter((key) => key.endsWith('File')); if ( !exactKeys(command, expectedCommandKeys) || command.schemaVersion !== 1 || command.operation !== 'cluster.prompt-output-key.verify-recovery' || fileFields.some( (key) => typeof command[key] !== 'string' || !command[key].startsWith(`${WORKSPACE}/`), ) || !Array.isArray(command.approverPublicKeyFiles) || command.approverPublicKeyFiles.length !== 2 || command.approverPublicKeyFiles.some( (entry) => !exactKeys(entry, ['filePath', 'userId']) || typeof entry.userId !== 'string' || typeof entry.filePath !== 'string' || !entry.filePath.startsWith(`${WORKSPACE}/`), ) ) { finding( findings, 'QL3_RECOVERY_COMMAND', 'recovery command must bind the exact read-only workspace', ); } return { schemaVersion: 1, compatible: findings.length === 0, namespace: NAMESPACE, serviceAccount: NAME, network: 'deny-all', databaseAuthority: false, kubernetesApiAuthority: false, kmsAuthority: false, workspaceReadOnly: true, findings, }; } if (require.main === module) { const report = auditPromptOutputExternalRecoveryDeployment(); process.stdout.write(`${JSON.stringify(report, null, 2)}\n`); if (!report.compatible) process.exitCode = 1; } module.exports = { auditPromptOutputExternalRecoveryDeployment };