Files
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

212 lines
7.5 KiB
Python

#!/usr/bin/env python3
"""Agent for verifying container image provenance using Sigstore Cosign."""
import json
import argparse
import subprocess
from datetime import datetime
def run_cosign(args_list):
"""Run a cosign CLI command and return output."""
cmd = ["cosign"] + args_list
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return {
"stdout": result.stdout.strip(),
"stderr": result.stderr.strip(),
"returncode": result.returncode,
}
def verify_image(image_ref, key=None, certificate_identity=None, certificate_oidc_issuer=None):
"""Verify a container image signature with Cosign."""
args = ["verify"]
if key:
args.extend(["--key", key])
elif certificate_identity and certificate_oidc_issuer:
args.extend(["--certificate-identity", certificate_identity,
"--certificate-oidc-issuer", certificate_oidc_issuer])
args.append(image_ref)
result = run_cosign(args)
verified = result["returncode"] == 0
attestations = []
if verified and result["stdout"]:
try:
attestations = json.loads(result["stdout"])
except json.JSONDecodeError:
pass
return {
"image": image_ref,
"verified": verified,
"attestations": attestations if isinstance(attestations, list) else [attestations],
"error": result["stderr"] if not verified else None,
}
def verify_attestation(image_ref, predicate_type, key=None):
"""Verify an in-toto attestation attached to an image."""
args = ["verify-attestation", "--type", predicate_type]
if key:
args.extend(["--key", key])
args.append(image_ref)
result = run_cosign(args)
return {
"image": image_ref,
"predicate_type": predicate_type,
"verified": result["returncode"] == 0,
"output": result["stdout"][:500] if result["stdout"] else None,
"error": result["stderr"] if result["returncode"] != 0 else None,
}
def sign_image(image_ref, key=None, keyless=False):
"""Sign a container image with Cosign."""
args = ["sign"]
if key:
args.extend(["--key", key])
elif keyless:
args.append("--yes")
args.append(image_ref)
result = run_cosign(args)
return {
"image": image_ref,
"signed": result["returncode"] == 0,
"error": result["stderr"] if result["returncode"] != 0 else None,
}
def triangulate_image(image_ref):
"""Get the signature and attestation image references."""
result = run_cosign(["triangulate", image_ref])
return {
"image": image_ref,
"signature_ref": result["stdout"] if result["returncode"] == 0 else None,
}
def audit_registry_images(images_list, key=None, identity=None, issuer=None):
"""Audit multiple container images for valid signatures."""
results = []
for image in images_list:
result = verify_image(image, key=key, certificate_identity=identity,
certificate_oidc_issuer=issuer)
result["severity"] = "INFO" if result["verified"] else "HIGH"
results.append(result)
signed = sum(1 for r in results if r["verified"])
return {
"total_images": len(results),
"signed": signed,
"unsigned": len(results) - signed,
"signing_rate": round(signed / len(results) * 100, 1) if results else 0,
"details": results,
}
def generate_kyverno_policy(image_patterns, key=None, identity=None, issuer=None):
"""Generate Kyverno ClusterPolicy for image verification."""
policy = {
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "verify-image-signatures"},
"spec": {
"validationFailureAction": "Enforce",
"webhookTimeoutSeconds": 30,
"rules": [{
"name": "verify-cosign-signature",
"match": {"any": [{"resources": {"kinds": ["Pod"]}}]},
"verifyImages": [{
"imageReferences": image_patterns,
"attestors": [{
"entries": [{
"keyless": {
"subject": identity or "*",
"issuer": issuer or "https://token.actions.githubusercontent.com",
}
}] if not key else [{"keys": {"publicKeys": key}}]
}],
}],
}],
},
}
return policy
def generate_cosign_ci_workflow(image_ref, registry):
"""Generate GitHub Actions workflow for Cosign signing."""
return f"""name: Sign Container Image
on:
push:
branches: [main]
jobs:
sign:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
- uses: actions/checkout@v4
- uses: sigstore/cosign-installer@v3
- name: Login to registry
uses: docker/login-action@v3
with:
registry: {registry}
- name: Build and push
run: |
docker build -t {image_ref} .
docker push {image_ref}
- name: Sign image (keyless)
run: cosign sign --yes {image_ref}
- name: Verify signature
run: cosign verify --certificate-identity-regexp=".*" --certificate-oidc-issuer="https://token.actions.githubusercontent.com" {image_ref}
"""
def main():
parser = argparse.ArgumentParser(description="Cosign Image Provenance Agent")
parser.add_argument("--verify", help="Image to verify")
parser.add_argument("--sign", help="Image to sign")
parser.add_argument("--audit", nargs="+", help="Images to audit for signatures")
parser.add_argument("--key", help="Cosign public key for verification")
parser.add_argument("--identity", help="Certificate identity for keyless verification")
parser.add_argument("--issuer", help="OIDC issuer for keyless verification")
parser.add_argument("--gen-policy", nargs="+", help="Image patterns for Kyverno policy")
parser.add_argument("--gen-workflow", help="Image ref for CI workflow generation")
parser.add_argument("--output", default="cosign_provenance_report.json")
args = parser.parse_args()
report = {"generated_at": datetime.utcnow().isoformat(), "results": {}}
if args.verify:
result = verify_image(args.verify, key=args.key,
certificate_identity=args.identity,
certificate_oidc_issuer=args.issuer)
report["results"]["verification"] = result
status = "VERIFIED" if result["verified"] else "FAILED"
print(f"[+] {args.verify}: {status}")
if args.audit:
result = audit_registry_images(args.audit, key=args.key,
identity=args.identity, issuer=args.issuer)
report["results"]["audit"] = result
print(f"[+] Audit: {result['signed']}/{result['total_images']} signed")
if args.gen_policy:
policy = generate_kyverno_policy(args.gen_policy, key=args.key,
identity=args.identity, issuer=args.issuer)
report["results"]["kyverno_policy"] = policy
print("[+] Kyverno policy generated")
if args.gen_workflow:
workflow = generate_cosign_ci_workflow(args.gen_workflow, "ghcr.io")
report["results"]["workflow"] = workflow
print("[+] CI workflow generated")
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"[+] Report saved to {args.output}")
if __name__ == "__main__":
main()