mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-18 21:49:40 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
in-toto Supply Chain Verification Tool
|
||||
|
||||
Verifies container image supply chain integrity by checking
|
||||
in-toto link metadata against the defined layout policy.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def compute_file_hash(file_path: str, algorithm: str = "sha256") -> str:
|
||||
"""Compute the hash of a file."""
|
||||
h = hashlib.new(algorithm)
|
||||
with open(file_path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(8192), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def find_link_files(link_dir: str) -> list[dict]:
|
||||
"""Find and parse all in-toto link metadata files."""
|
||||
link_path = Path(link_dir)
|
||||
if not link_path.exists():
|
||||
print(f"[ERROR] Link directory not found: {link_dir}")
|
||||
return []
|
||||
|
||||
links = []
|
||||
for link_file in link_path.glob("*.link"):
|
||||
try:
|
||||
with open(link_file) as f:
|
||||
data = json.load(f)
|
||||
links.append({
|
||||
"file": str(link_file),
|
||||
"step_name": data.get("signed", {}).get("name", "unknown"),
|
||||
"materials": data.get("signed", {}).get("materials", {}),
|
||||
"products": data.get("signed", {}).get("products", {}),
|
||||
"command": data.get("signed", {}).get("command", []),
|
||||
"byproducts": data.get("signed", {}).get("byproducts", {}),
|
||||
"signatures": data.get("signatures", []),
|
||||
})
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
print(f"[WARN] Failed to parse {link_file}: {e}")
|
||||
|
||||
return links
|
||||
|
||||
|
||||
def verify_artifact_chain(links: list[dict]) -> list[dict]:
|
||||
"""Verify that artifact hashes chain correctly between steps."""
|
||||
findings = []
|
||||
products_by_step = {}
|
||||
|
||||
for link in sorted(links, key=lambda x: x["step_name"]):
|
||||
products_by_step[link["step_name"]] = link["products"]
|
||||
|
||||
for link in links:
|
||||
for material_path, material_hashes in link["materials"].items():
|
||||
found = False
|
||||
for step_name, products in products_by_step.items():
|
||||
if step_name == link["step_name"]:
|
||||
continue
|
||||
if material_path in products:
|
||||
product_hashes = products[material_path]
|
||||
for algo, expected_hash in material_hashes.items():
|
||||
actual_hash = product_hashes.get(algo, "")
|
||||
if actual_hash and actual_hash != expected_hash:
|
||||
findings.append({
|
||||
"severity": "CRITICAL",
|
||||
"type": "hash_mismatch",
|
||||
"step": link["step_name"],
|
||||
"artifact": material_path,
|
||||
"expected": expected_hash[:16] + "...",
|
||||
"actual": actual_hash[:16] + "...",
|
||||
"description": f"Artifact {material_path} hash mismatch between steps"
|
||||
})
|
||||
elif actual_hash:
|
||||
found = True
|
||||
if not found and link["materials"]:
|
||||
findings.append({
|
||||
"severity": "WARNING",
|
||||
"type": "untracked_material",
|
||||
"step": link["step_name"],
|
||||
"artifact": material_path,
|
||||
"description": f"Material {material_path} not found in any prior step products"
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def verify_signatures(links: list[dict], trusted_keys: list[str]) -> list[dict]:
|
||||
"""Verify that all link signatures come from trusted keys."""
|
||||
findings = []
|
||||
for link in links:
|
||||
if not link["signatures"]:
|
||||
findings.append({
|
||||
"severity": "CRITICAL",
|
||||
"type": "missing_signature",
|
||||
"step": link["step_name"],
|
||||
"description": f"Step {link['step_name']} has no signatures"
|
||||
})
|
||||
continue
|
||||
|
||||
for sig in link["signatures"]:
|
||||
keyid = sig.get("keyid", "")
|
||||
if trusted_keys and keyid not in trusted_keys:
|
||||
findings.append({
|
||||
"severity": "HIGH",
|
||||
"type": "untrusted_key",
|
||||
"step": link["step_name"],
|
||||
"keyid": keyid[:16] + "...",
|
||||
"description": f"Step {link['step_name']} signed by untrusted key"
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_required_steps(links: list[dict], required_steps: list[str]) -> list[dict]:
|
||||
"""Check that all required pipeline steps have link metadata."""
|
||||
findings = []
|
||||
observed_steps = {link["step_name"] for link in links}
|
||||
|
||||
for step in required_steps:
|
||||
if step not in observed_steps:
|
||||
findings.append({
|
||||
"severity": "CRITICAL",
|
||||
"type": "missing_step",
|
||||
"step": step,
|
||||
"description": f"Required step '{step}' has no link metadata"
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def run_in_toto_verify(layout_path: str, layout_key: str, link_dir: str) -> dict:
|
||||
"""Run the official in-toto-verify command."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
"in-toto-verify",
|
||||
"--layout", layout_path,
|
||||
"--layout-key", layout_key,
|
||||
"--link-dir", link_dir,
|
||||
],
|
||||
capture_output=True, text=True, timeout=60
|
||||
)
|
||||
return {
|
||||
"success": result.returncode == 0,
|
||||
"stdout": result.stdout.strip(),
|
||||
"stderr": result.stderr.strip(),
|
||||
"returncode": result.returncode
|
||||
}
|
||||
except FileNotFoundError:
|
||||
return {"success": False, "error": "in-toto-verify not found in PATH"}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"success": False, "error": "Verification timed out"}
|
||||
|
||||
|
||||
def generate_report(links: list[dict], chain_findings: list[dict],
|
||||
sig_findings: list[dict], step_findings: list[dict],
|
||||
verify_result: dict, output_format: str = "text") -> str:
|
||||
"""Generate a comprehensive verification report."""
|
||||
all_findings = chain_findings + sig_findings + step_findings
|
||||
critical_count = sum(1 for f in all_findings if f["severity"] == "CRITICAL")
|
||||
high_count = sum(1 for f in all_findings if f["severity"] == "HIGH")
|
||||
|
||||
if output_format == "json":
|
||||
report = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"verification_passed": verify_result.get("success", False) and critical_count == 0,
|
||||
"steps_found": len(links),
|
||||
"findings": {
|
||||
"critical": critical_count,
|
||||
"high": high_count,
|
||||
"total": len(all_findings),
|
||||
"details": all_findings
|
||||
},
|
||||
"in_toto_verify": verify_result,
|
||||
"steps": [{"name": l["step_name"], "command": l["command"],
|
||||
"materials": len(l["materials"]), "products": len(l["products"])}
|
||||
for l in links]
|
||||
}
|
||||
return json.dumps(report, indent=2)
|
||||
|
||||
lines = []
|
||||
lines.append("=" * 70)
|
||||
lines.append("IN-TOTO SUPPLY CHAIN VERIFICATION REPORT")
|
||||
lines.append(f"Generated: {datetime.utcnow().isoformat()}")
|
||||
lines.append("=" * 70)
|
||||
|
||||
passed = verify_result.get("success", False) and critical_count == 0
|
||||
lines.append(f"\nVerification Result: {'PASSED' if passed else 'FAILED'}")
|
||||
lines.append(f"Steps Found: {len(links)}")
|
||||
|
||||
lines.append("\n## Pipeline Steps")
|
||||
for link in sorted(links, key=lambda x: x["step_name"]):
|
||||
lines.append(f" Step: {link['step_name']}")
|
||||
lines.append(f" Command: {' '.join(link['command'])}")
|
||||
lines.append(f" Materials: {len(link['materials'])} | Products: {len(link['products'])}")
|
||||
lines.append(f" Signatures: {len(link['signatures'])}")
|
||||
|
||||
if all_findings:
|
||||
lines.append(f"\n## Findings ({len(all_findings)} total)")
|
||||
for f in sorted(all_findings, key=lambda x: x["severity"]):
|
||||
lines.append(f" [{f['severity']}] {f['description']}")
|
||||
|
||||
if verify_result.get("error"):
|
||||
lines.append(f"\n## in-toto-verify Error")
|
||||
lines.append(f" {verify_result['error']}")
|
||||
|
||||
lines.append("\n" + "=" * 70)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="in-toto Supply Chain Verification Tool")
|
||||
parser.add_argument("--layout", help="Path to in-toto layout file")
|
||||
parser.add_argument("--layout-key", help="Path to layout signing public key")
|
||||
parser.add_argument("--link-dir", required=True, help="Directory containing link metadata")
|
||||
parser.add_argument("--required-steps", nargs="+", default=["checkout", "build", "scan"],
|
||||
help="Required pipeline steps")
|
||||
parser.add_argument("--trusted-keys", nargs="+", default=[], help="Trusted key IDs")
|
||||
parser.add_argument("--format", choices=["text", "json"], default="text")
|
||||
args = parser.parse_args()
|
||||
|
||||
links = find_link_files(args.link_dir)
|
||||
if not links:
|
||||
print("[ERROR] No link metadata found")
|
||||
sys.exit(1)
|
||||
|
||||
chain_findings = verify_artifact_chain(links)
|
||||
sig_findings = verify_signatures(links, args.trusted_keys)
|
||||
step_findings = check_required_steps(links, args.required_steps)
|
||||
|
||||
verify_result = {}
|
||||
if args.layout and args.layout_key:
|
||||
verify_result = run_in_toto_verify(args.layout, args.layout_key, args.link_dir)
|
||||
else:
|
||||
verify_result = {"success": None, "note": "Layout verification skipped (no --layout provided)"}
|
||||
|
||||
report = generate_report(links, chain_findings, sig_findings, step_findings, verify_result, args.format)
|
||||
print(report)
|
||||
|
||||
critical_count = sum(1 for f in chain_findings + sig_findings + step_findings if f["severity"] == "CRITICAL")
|
||||
sys.exit(1 if critical_count > 0 else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user