#!/usr/bin/env python3 """Reject mutable container references and checked-in credential values.""" from pathlib import Path import argparse import re import sys DIGEST = re.compile(r"^[^\s@]+@sha256:[0-9a-f]{64}$") FROM = re.compile(r"^\s*FROM(?:\s+--platform=\S+)?\s+(\S+)") IMAGE = re.compile(r"^\s*image:\s*(\S+)\s*$") SECRET_VALUE = re.compile(r"^\s*(?:password|token|private[-_ ]?key|publisher[-_ ]?key):\s*\S+", re.I) def check_text(path: Path, text: str, concrete: bool) -> list[str]: errors = [] for line_number, line in enumerate(text.splitlines(), 1): from_match = FROM.match(line) image_match = IMAGE.match(line) reference = from_match.group(1) if from_match else image_match.group(1) if image_match else None if from_match and reference: reference = reference.split(" AS ", 1)[0].split(" as ", 1)[0] # A bare name in a later Docker stage is an internal stage alias, not # an independently fetched image and therefore needs no digest. internal_stage = bool(from_match and reference and "/" not in reference and "@" not in reference and ":" not in reference) if reference and not internal_stage and not DIGEST.fullmatch(reference): errors.append(f"{path}:{line_number}: image is not digest-pinned: {reference}") if concrete and reference and "@sha256:" in reference: digest = reference.rsplit("@sha256:", 1)[1] if set(digest) == {"0"}: errors.append(f"{path}:{line_number}: template digest is not a release artifact") if SECRET_VALUE.match(line): errors.append(f"{path}:{line_number}: possible plaintext credential") return errors def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--dockerfile", type=Path, default=Path("Dockerfile")) parser.add_argument("--manifest-dir", type=Path, default=Path("deploy/k8s")) parser.add_argument("--require-concrete", action="store_true") args = parser.parse_args() errors = check_text(args.dockerfile, args.dockerfile.read_text(), args.require_concrete) for path in sorted(args.manifest_dir.rglob("*.y*ml")): errors.extend(check_text(path, path.read_text(), args.require_concrete)) for error in errors: print(error, file=sys.stderr) return 1 if errors else 0 if __name__ == "__main__": raise SystemExit(main())