mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
feat: enforce supply chain policy
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user