Files
CosmicClash/scripts/verify_supply_chain.py
T
2026-08-31 21:32:03 +01:00

54 lines
2.4 KiB
Python

#!/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())