mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
c21af3347e
- 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
212 lines
7.5 KiB
Python
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()
|