#!/usr/bin/env node 'use strict'; const fs = require('node:fs'); const path = require('node:path'); const { TextDecoder } = require('node:util'); const FIXTURE = 'qinglong/image-os-vulnerability-exceptions@v1'; const IMAGES = Object.freeze([ 'admin', 'control', 'control-ai', 'local', 'local-console', 'local-operator', 'worker', ]); const MAX_POLICY_BYTES = 256 * 1024; const MAX_EXCEPTIONS = 128; const MAX_EXCEPTION_DAYS = 30; const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/; const CVE_PATTERN = /^CVE-[0-9]{4}-[0-9]{4,}$/; const OWNER_PATTERN = /^[a-z0-9][a-z0-9._/-]{1,127}$/; const TICKET_PATTERN = /^[A-Z][A-Z0-9]{1,15}-[1-9][0-9]{0,9}$/; const PURL_PATTERN = /^pkg:(?:apk|deb|rpm)\/[A-Za-z0-9._~%+-]+\/[A-Za-z0-9._~%+-]+@[A-Za-z0-9._~%+:-]+$/; const DEFAULT_ROOT = path.resolve(__dirname, '..'); const POLICY_PATH = 'deploy/containers/ql3-os-vulnerability-exceptions.json'; class ImageOsVulnerabilityPolicyError extends Error { constructor(message) { super(`QingLong image OS vulnerability policy failed: ${message}`); this.name = 'ImageOsVulnerabilityPolicyError'; } } function fail(message) { throw new ImageOsVulnerabilityPolicyError(message); } function exactKeys(value, keys) { return ( value !== null && typeof value === 'object' && !Array.isArray(value) && JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort()) ); } function uniqueSorted(values) { return ( Array.isArray(values) && values.length > 0 && new Set(values).size === values.length && JSON.stringify(values) === JSON.stringify([...values].sort()) ); } function boundedText(value, minimum, maximum) { return ( typeof value === 'string' && value.length >= minimum && value.length <= maximum && !CONTROL_PATTERN.test(value) ); } function utcDay(value) { if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) { return null; } const milliseconds = Date.parse(`${value}T00:00:00.000Z`); return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString().slice(0, 10) === value ? milliseconds : null; } function auditImageOsVulnerabilityPolicy( policy, dependencies = { now: Date.now }, ) { if ( !exactKeys(dependencies, ['now']) || typeof dependencies.now !== 'function' ) { fail('clock is invalid'); } const nowMs = dependencies.now(); if (!Number.isSafeInteger(nowMs) || nowMs < 0) fail('clock is invalid'); const todayMs = Date.parse(new Date(nowMs).toISOString().slice(0, 10)); const findings = []; const add = (code, id = null) => findings.push(Object.freeze({ code, id })); if ( !exactKeys(policy, ['schemaVersion', 'fixture', 'exceptions']) || policy?.schemaVersion !== 1 || policy?.fixture !== FIXTURE || !Array.isArray(policy?.exceptions) || policy.exceptions.length > MAX_EXCEPTIONS ) { add('QL3_IMAGE_OS_VULNERABILITY_POLICY_SHAPE'); return Object.freeze({ compatible: false, findings: Object.freeze(findings), exceptionCount: 0, imageExceptionCounts: Object.freeze({ admin: 0, control: 0, 'control-ai': 0, local: 0, 'local-console': 0, 'local-operator': 0, worker: 0, }), }); } const counts = { admin: 0, control: 0, 'control-ai': 0, local: 0, 'local-console': 0, 'local-operator': 0, worker: 0, }; const seen = new Set(); let previousId = ''; for (const exception of policy.exceptions) { const id = exception?.id ?? null; if ( !exactKeys(exception, [ 'id', 'images', 'purls', 'owner', 'ticket', 'expiresOn', 'rationale', ]) || !CVE_PATTERN.test(id ?? '') || seen.has(id) || id <= previousId ) { add('QL3_IMAGE_OS_VULNERABILITY_EXCEPTION_ID', id); continue; } seen.add(id); previousId = id; if ( !uniqueSorted(exception.images) || exception.images.some((image) => !IMAGES.includes(image)) ) { add('QL3_IMAGE_OS_VULNERABILITY_EXCEPTION_IMAGES', id); } else { for (const image of exception.images) counts[image] += 1; } if ( !uniqueSorted(exception.purls) || exception.purls.some((purl) => !PURL_PATTERN.test(purl)) ) { add('QL3_IMAGE_OS_VULNERABILITY_EXCEPTION_PURLS', id); } if (!OWNER_PATTERN.test(exception.owner ?? '')) { add('QL3_IMAGE_OS_VULNERABILITY_EXCEPTION_OWNER', id); } if (!TICKET_PATTERN.test(exception.ticket ?? '')) { add('QL3_IMAGE_OS_VULNERABILITY_EXCEPTION_TICKET', id); } if (!boundedText(exception.rationale, 20, 512)) { add('QL3_IMAGE_OS_VULNERABILITY_EXCEPTION_RATIONALE', id); } const expiryMs = utcDay(exception.expiresOn); if ( expiryMs === null || expiryMs <= todayMs || expiryMs - todayMs > MAX_EXCEPTION_DAYS * 24 * 60 * 60 * 1000 ) { add('QL3_IMAGE_OS_VULNERABILITY_EXCEPTION_EXPIRY', id); } } return Object.freeze({ compatible: findings.length === 0, findings: Object.freeze(findings), exceptionCount: policy.exceptions.length, imageExceptionCounts: Object.freeze(counts), }); } function readPolicy(root = DEFAULT_ROOT) { const filePath = path.join(path.resolve(root), POLICY_PATH); const stat = fs.lstatSync(filePath); if ( !stat.isFile() || stat.isSymbolicLink() || stat.size < 2 || stat.size > MAX_POLICY_BYTES || fs.realpathSync(filePath) !== filePath ) { fail('policy must be one canonical bounded regular file'); } let bytes; try { bytes = fs.readFileSync(filePath); return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)); } catch (error) { if (error instanceof ImageOsVulnerabilityPolicyError) throw error; fail('policy must contain UTF-8 JSON'); } finally { if (bytes) bytes.fill(0); } } function renderTrivyIgnore(policy, image, dependencies = { now: Date.now }) { if (!IMAGES.includes(image)) fail('image is invalid'); const audit = auditImageOsVulnerabilityPolicy(policy, dependencies); if (!audit.compatible) fail('policy is incompatible'); const selected = policy.exceptions.filter((entry) => entry.images.includes(image), ); const lines = ['vulnerabilities:']; if (selected.length === 0) lines.push(' []'); for (const exception of selected) { lines.push(` - id: ${JSON.stringify(exception.id)}`); lines.push(' purls:'); for (const purl of exception.purls) { lines.push(` - ${JSON.stringify(purl)}`); } lines.push(` expired_at: ${exception.expiresOn}`); lines.push( ` statement: ${JSON.stringify( `owner=${exception.owner}; ticket=${exception.ticket}; rationale=${exception.rationale}`, )}`, ); } return `${lines.join('\n')}\n`; } function writeNoReplace(filePath, value) { if ( typeof filePath !== 'string' || !path.isAbsolute(filePath) || CONTROL_PATTERN.test(filePath) || fs.existsSync(filePath) || fs.realpathSync(path.dirname(filePath)) !== path.dirname(filePath) ) { fail('output path must be unused in one canonical directory'); } let descriptor = -1; const bytes = Buffer.from(value, 'utf8'); try { descriptor = fs.openSync( filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600, ); fs.writeFileSync(descriptor, bytes); fs.fsyncSync(descriptor); } finally { bytes.fill(0); if (descriptor >= 0) fs.closeSync(descriptor); } } function parseArguments(argv) { if (argv.length === 0) return Object.freeze({ mode: 'audit' }); const values = {}; for (const argument of argv) { if (argument === '--') continue; const match = /^--([a-z-]+)=(.+)$/.exec(argument); if (!match || Object.hasOwn(values, match[1])) fail('arguments are invalid'); values[match[1]] = match[2]; } if ( JSON.stringify(Object.keys(values).sort()) !== JSON.stringify(['image', 'output']) ) { fail('arguments are invalid'); } return Object.freeze({ mode: 'render', image: values.image, output: values.output, }); } function runCli(argv, root = DEFAULT_ROOT, dependencies = { now: Date.now }) { const options = parseArguments(argv); const policy = readPolicy(root); const audit = auditImageOsVulnerabilityPolicy(policy, dependencies); if (!audit.compatible) fail('policy is incompatible'); if (options.mode === 'render') { writeNoReplace( options.output, renderTrivyIgnore(policy, options.image, dependencies), ); } const report = { schemaVersion: 1, fixture: FIXTURE, compatible: true, exceptionCount: audit.exceptionCount, imageExceptionCounts: audit.imageExceptionCounts, }; process.stdout.write(`${JSON.stringify(report)}\n`); return report; } if (require.main === module) { try { runCli(process.argv.slice(2)); } catch (error) { process.stderr.write( `${error instanceof Error ? error.message : 'policy audit failed'}\n`, ); process.exitCode = 1; } } module.exports = { FIXTURE, IMAGES, MAX_EXCEPTION_DAYS, ImageOsVulnerabilityPolicyError, auditImageOsVulnerabilityPolicy, parseArguments, readPolicy, renderTrivyIgnore, runCli, writeNoReplace, };