mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 14:14:56 +03:00
190 lines
6.0 KiB
Python
190 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Cosign Image Provenance Manager - Sign, verify, and audit container
|
|
image signatures using Sigstore Cosign.
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
|
|
def run_cosign(args: list) -> dict:
|
|
"""Execute cosign command and return output."""
|
|
cmd = ["cosign"] + args
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
return {
|
|
"stdout": result.stdout,
|
|
"stderr": result.stderr,
|
|
"returncode": result.returncode,
|
|
}
|
|
|
|
|
|
def sign_image(image: str, key: str = None, annotations: dict = None, keyless: bool = False) -> bool:
|
|
"""Sign a container image."""
|
|
args = ["sign"]
|
|
if key:
|
|
args.extend(["--key", key])
|
|
if keyless:
|
|
args.append("--yes")
|
|
if annotations:
|
|
for k, v in annotations.items():
|
|
args.extend(["-a", f"{k}={v}"])
|
|
args.append(image)
|
|
|
|
result = run_cosign(args)
|
|
if result["returncode"] == 0:
|
|
print(f"Successfully signed: {image}")
|
|
return True
|
|
else:
|
|
print(f"Failed to sign: {result['stderr']}", file=sys.stderr)
|
|
return False
|
|
|
|
|
|
def verify_image(image: str, key: str = None, identity: str = None,
|
|
issuer: str = None) -> dict:
|
|
"""Verify a container image signature."""
|
|
args = ["verify"]
|
|
if key:
|
|
args.extend(["--key", key])
|
|
if identity:
|
|
args.extend(["--certificate-identity", identity])
|
|
if issuer:
|
|
args.extend(["--certificate-oidc-issuer", issuer])
|
|
args.append(image)
|
|
|
|
result = run_cosign(args)
|
|
verified = result["returncode"] == 0
|
|
|
|
signatures = []
|
|
if verified and result["stdout"].strip():
|
|
try:
|
|
signatures = json.loads(result["stdout"])
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
return {
|
|
"image": image,
|
|
"verified": verified,
|
|
"signatures": signatures,
|
|
"error": result["stderr"] if not verified else None,
|
|
}
|
|
|
|
|
|
def verify_attestation(image: str, att_type: str, key: str = None,
|
|
identity: str = None, issuer: str = None) -> dict:
|
|
"""Verify an attestation on a container image."""
|
|
args = ["verify-attestation", "--type", att_type]
|
|
if key:
|
|
args.extend(["--key", key])
|
|
if identity:
|
|
args.extend(["--certificate-identity", identity])
|
|
if issuer:
|
|
args.extend(["--certificate-oidc-issuer", issuer])
|
|
args.append(image)
|
|
|
|
result = run_cosign(args)
|
|
return {
|
|
"image": image,
|
|
"type": att_type,
|
|
"verified": result["returncode"] == 0,
|
|
"output": result["stdout"],
|
|
"error": result["stderr"] if result["returncode"] != 0 else None,
|
|
}
|
|
|
|
|
|
def audit_images(images: list, key: str = None, identity: str = None,
|
|
issuer: str = None) -> list:
|
|
"""Audit multiple images for valid signatures."""
|
|
results = []
|
|
for image in images:
|
|
result = verify_image(image, key=key, identity=identity, issuer=issuer)
|
|
results.append(result)
|
|
return results
|
|
|
|
|
|
def generate_report(audit_results: list) -> str:
|
|
"""Generate markdown audit report."""
|
|
signed = sum(1 for r in audit_results if r["verified"])
|
|
total = len(audit_results)
|
|
|
|
report = f"""# Image Signature Audit Report
|
|
|
|
**Total Images:** {total}
|
|
**Signed:** {signed}
|
|
**Unsigned:** {total - signed}
|
|
|
|
## Results
|
|
|
|
| Image | Signed | Signatures | Error |
|
|
|-------|--------|------------|-------|
|
|
"""
|
|
for r in audit_results:
|
|
status = "YES" if r["verified"] else "NO"
|
|
sig_count = len(r.get("signatures", []))
|
|
error = r.get("error", "")[:50] if r.get("error") else "-"
|
|
report += f"| `{r['image']}` | {status} | {sig_count} | {error} |\n"
|
|
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Cosign Image Provenance Manager")
|
|
subparsers = parser.add_subparsers(dest="command")
|
|
|
|
sign_cmd = subparsers.add_parser("sign", help="Sign an image")
|
|
sign_cmd.add_argument("image", help="Image reference")
|
|
sign_cmd.add_argument("--key", help="Signing key path")
|
|
sign_cmd.add_argument("--keyless", action="store_true", help="Use keyless signing")
|
|
sign_cmd.add_argument("--annotation", "-a", action="append", help="key=value annotations")
|
|
|
|
verify_cmd = subparsers.add_parser("verify", help="Verify image signature")
|
|
verify_cmd.add_argument("image", help="Image reference")
|
|
verify_cmd.add_argument("--key", help="Public key path")
|
|
verify_cmd.add_argument("--identity", help="Certificate identity")
|
|
verify_cmd.add_argument("--issuer", help="OIDC issuer")
|
|
|
|
audit_cmd = subparsers.add_parser("audit", help="Audit multiple images")
|
|
audit_cmd.add_argument("--images-file", required=True, help="File with image refs (one per line)")
|
|
audit_cmd.add_argument("--key", help="Public key path")
|
|
audit_cmd.add_argument("--identity", help="Certificate identity")
|
|
audit_cmd.add_argument("--issuer", help="OIDC issuer")
|
|
audit_cmd.add_argument("--report", help="Output report path")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "sign":
|
|
annotations = {}
|
|
if args.annotation:
|
|
for a in args.annotation:
|
|
k, v = a.split("=", 1)
|
|
annotations[k] = v
|
|
sign_image(args.image, key=args.key, annotations=annotations,
|
|
keyless=args.keyless)
|
|
|
|
elif args.command == "verify":
|
|
result = verify_image(args.image, key=args.key,
|
|
identity=args.identity, issuer=args.issuer)
|
|
print(json.dumps(result, indent=2))
|
|
sys.exit(0 if result["verified"] else 1)
|
|
|
|
elif args.command == "audit":
|
|
images = Path(args.images_file).read_text().strip().split("\n")
|
|
results = audit_images(images, key=args.key,
|
|
identity=args.identity, issuer=args.issuer)
|
|
report = generate_report(results)
|
|
if args.report:
|
|
Path(args.report).write_text(report)
|
|
print(f"Report written to {args.report}")
|
|
else:
|
|
print(report)
|
|
|
|
else:
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|