Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/verify-container-image-pins.mjs
T

80 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const DIGEST_PATTERN = /@sha256:[0-9a-f]{64}(?=[}\s"']|$)/;
const SKIP_DIRECTORIES = new Set([".git", "node_modules", "dist", "build", "coverage"]);
function listFiles(rootDir) {
const files = [];
const stack = [rootDir];
while (stack.length > 0) {
const current = stack.pop();
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (!SKIP_DIRECTORIES.has(entry.name)) stack.push(path.join(current, entry.name));
continue;
}
if (!entry.isFile()) continue;
if (/^(?:docker-)?compose.*\.ya?ml$/i.test(entry.name) || /^Dockerfile(?:\..+)?$/.test(entry.name)) {
files.push(path.join(current, entry.name));
}
}
}
return files;
}
export function inspectContainerFile(source, relativePath) {
const failures = [];
const passes = [];
const stages = new Set();
for (const [index, line] of source.split(/\r?\n/).entries()) {
const image = line.match(/^\s*image:\s*(.+?)\s*(?:#.*)?$/i);
if (image) {
if (DIGEST_PATTERN.test(image[1])) {
passes.push(`${relativePath}:${index + 1}: Compose image pinned by digest`);
} else {
failures.push(`${relativePath}:${index + 1}: Compose image must use an immutable sha256 digest (${image[1]})`);
}
}
const from = line.match(/^\s*FROM\s+(?:--platform=\S+\s+)?(\S+)(?:\s+AS\s+(\S+))?\s*$/i);
if (!from) continue;
const [, base, stage] = from;
if (stages.has(base)) {
passes.push(`${relativePath}:${index + 1}: Docker stage derives from local stage ${base}`);
} else if (DIGEST_PATTERN.test(base)) {
passes.push(`${relativePath}:${index + 1}: Docker base image pinned by digest`);
} else {
failures.push(`${relativePath}:${index + 1}: Docker base image must use an immutable sha256 digest (${base})`);
}
if (stage) stages.add(stage);
}
return { failures, passes };
}
export function verifyContainerImagePins(rootDir = process.cwd()) {
const result = { failures: [], passes: [] };
for (const file of listFiles(rootDir)) {
const relativePath = path.relative(rootDir, file);
const inspected = inspectContainerFile(fs.readFileSync(file, "utf8"), relativePath);
result.failures.push(...inspected.failures);
result.passes.push(...inspected.passes);
}
return result;
}
const isCli = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
if (isCli) {
const result = verifyContainerImagePins();
for (const failure of result.failures) process.stderr.write(`FAIL ${failure}\n`);
process.stdout.write(
`${result.failures.length === 0 ? "PASS" : "FAIL"} container image pin verification\n` +
`Summary: ${result.passes.length} pinned/local image references, ${result.failures.length} failures\n`,
);
if (result.failures.length > 0) process.exitCode = 1;
}