mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-10 21:24:56 +03:00
373 lines
13 KiB
Python
373 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tetragon Runtime Security Event Analyzer
|
|
|
|
Parses Tetragon JSON event logs and generates security reports
|
|
including process execution anomalies, policy violations, and
|
|
container escape attempt detection.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import subprocess
|
|
import argparse
|
|
from datetime import datetime, timedelta
|
|
from collections import Counter, defaultdict
|
|
from pathlib import Path
|
|
|
|
|
|
def run_kubectl_command(command: list[str]) -> str:
|
|
"""Execute a kubectl command and return output."""
|
|
try:
|
|
result = subprocess.run(
|
|
command, capture_output=True, text=True, timeout=30
|
|
)
|
|
if result.returncode != 0:
|
|
print(f"[ERROR] kubectl command failed: {result.stderr.strip()}")
|
|
return ""
|
|
return result.stdout.strip()
|
|
except subprocess.TimeoutExpired:
|
|
print("[ERROR] kubectl command timed out")
|
|
return ""
|
|
except FileNotFoundError:
|
|
print("[ERROR] kubectl not found in PATH")
|
|
return ""
|
|
|
|
|
|
def get_tetragon_status() -> dict:
|
|
"""Check Tetragon DaemonSet health."""
|
|
output = run_kubectl_command([
|
|
"kubectl", "get", "ds", "tetragon", "-n", "kube-system",
|
|
"-o", "jsonpath={.status.desiredNumberScheduled},{.status.numberReady}"
|
|
])
|
|
if not output:
|
|
return {"healthy": False, "desired": 0, "ready": 0}
|
|
parts = output.split(",")
|
|
desired = int(parts[0]) if len(parts) > 0 and parts[0] else 0
|
|
ready = int(parts[1]) if len(parts) > 1 and parts[1] else 0
|
|
return {"healthy": desired == ready and desired > 0, "desired": desired, "ready": ready}
|
|
|
|
|
|
def get_tracing_policies() -> list[dict]:
|
|
"""List all TracingPolicies deployed in the cluster."""
|
|
output = run_kubectl_command([
|
|
"kubectl", "get", "tracingpolicies", "-o", "json"
|
|
])
|
|
if not output:
|
|
return []
|
|
try:
|
|
data = json.loads(output)
|
|
policies = []
|
|
for item in data.get("items", []):
|
|
policies.append({
|
|
"name": item["metadata"]["name"],
|
|
"created": item["metadata"].get("creationTimestamp", "unknown"),
|
|
"kprobes": len(item.get("spec", {}).get("kprobes", [])),
|
|
"tracepoints": len(item.get("spec", {}).get("tracepoints", []))
|
|
})
|
|
return policies
|
|
except (json.JSONDecodeError, KeyError):
|
|
return []
|
|
|
|
|
|
def parse_tetragon_events(log_file: str) -> list[dict]:
|
|
"""Parse Tetragon JSON event log file."""
|
|
events = []
|
|
path = Path(log_file)
|
|
if not path.exists():
|
|
print(f"[ERROR] Log file not found: {log_file}")
|
|
return events
|
|
|
|
with open(path, "r") as f:
|
|
for line_num, line in enumerate(f, 1):
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
try:
|
|
event = json.loads(line)
|
|
events.append(event)
|
|
except json.JSONDecodeError:
|
|
print(f"[WARN] Skipping malformed JSON at line {line_num}")
|
|
return events
|
|
|
|
|
|
def classify_event(event: dict) -> str:
|
|
"""Classify a Tetragon event by type."""
|
|
if "process_exec" in event:
|
|
return "process_exec"
|
|
elif "process_exit" in event:
|
|
return "process_exit"
|
|
elif "process_kprobe" in event:
|
|
return "kprobe"
|
|
elif "process_tracepoint" in event:
|
|
return "tracepoint"
|
|
elif "process_lsm" in event:
|
|
return "lsm"
|
|
return "unknown"
|
|
|
|
|
|
def extract_process_info(event: dict) -> dict:
|
|
"""Extract process information from a Tetragon event."""
|
|
info = {"binary": "", "args": "", "namespace": "", "pod": "", "uid": 0, "action": ""}
|
|
for event_type in ["process_exec", "process_kprobe", "process_tracepoint"]:
|
|
if event_type in event:
|
|
process = event[event_type].get("process", {})
|
|
info["binary"] = process.get("binary", "")
|
|
info["args"] = process.get("arguments", "")
|
|
info["uid"] = process.get("uid", {}).get("value", 0)
|
|
pod_info = process.get("pod", {})
|
|
info["namespace"] = pod_info.get("namespace", "")
|
|
info["pod"] = pod_info.get("name", "")
|
|
if event_type == "process_kprobe":
|
|
info["action"] = event[event_type].get("action", "")
|
|
break
|
|
return info
|
|
|
|
|
|
def detect_suspicious_binaries(events: list[dict]) -> list[dict]:
|
|
"""Detect execution of known suspicious binaries."""
|
|
suspicious_binaries = {
|
|
"/bin/sh", "/bin/bash", "/bin/dash", "/usr/bin/curl",
|
|
"/usr/bin/wget", "/usr/bin/nc", "/usr/bin/ncat",
|
|
"/usr/bin/nmap", "/usr/bin/python", "/usr/bin/python3",
|
|
"/usr/bin/perl", "/usr/bin/ruby", "/usr/bin/gcc",
|
|
"/usr/bin/cc", "/usr/bin/make", "/usr/bin/xmrig",
|
|
"/tmp/xmrig", "/usr/bin/minerd",
|
|
"/usr/bin/sudo", "/bin/su", "/usr/bin/passwd",
|
|
"/usr/bin/nsenter", "/usr/bin/unshare"
|
|
}
|
|
findings = []
|
|
for event in events:
|
|
info = extract_process_info(event)
|
|
if info["binary"] in suspicious_binaries:
|
|
findings.append({
|
|
"severity": "HIGH" if info["binary"] in {"/usr/bin/xmrig", "/usr/bin/nsenter", "/usr/bin/unshare"} else "MEDIUM",
|
|
"binary": info["binary"],
|
|
"args": info["args"],
|
|
"namespace": info["namespace"],
|
|
"pod": info["pod"],
|
|
"description": f"Suspicious binary execution: {info['binary']}"
|
|
})
|
|
return findings
|
|
|
|
|
|
def detect_privilege_escalation(events: list[dict]) -> list[dict]:
|
|
"""Detect privilege escalation attempts from event data."""
|
|
findings = []
|
|
priv_esc_binaries = {"/usr/bin/sudo", "/bin/su", "/usr/bin/passwd", "/usr/bin/newgrp"}
|
|
for event in events:
|
|
info = extract_process_info(event)
|
|
if info["binary"] in priv_esc_binaries and info["namespace"]:
|
|
findings.append({
|
|
"severity": "CRITICAL",
|
|
"binary": info["binary"],
|
|
"namespace": info["namespace"],
|
|
"pod": info["pod"],
|
|
"description": f"Privilege escalation attempt via {info['binary']} in pod {info['pod']}"
|
|
})
|
|
if info["uid"] == 0 and info["binary"] and info["namespace"]:
|
|
findings.append({
|
|
"severity": "HIGH",
|
|
"binary": info["binary"],
|
|
"namespace": info["namespace"],
|
|
"pod": info["pod"],
|
|
"description": f"Process running as root (UID 0): {info['binary']}"
|
|
})
|
|
return findings
|
|
|
|
|
|
def detect_container_escape_attempts(events: list[dict]) -> list[dict]:
|
|
"""Detect potential container escape attempts."""
|
|
escape_indicators = {
|
|
"__x64_sys_setns": "Namespace manipulation (potential container escape)",
|
|
"__x64_sys_unshare": "Namespace unshare (potential privilege escalation)",
|
|
"__x64_sys_mount": "Mount syscall (potential host filesystem access)",
|
|
"__x64_sys_ptrace": "Ptrace syscall (potential process injection)",
|
|
}
|
|
findings = []
|
|
for event in events:
|
|
if "process_kprobe" in event:
|
|
function_name = event["process_kprobe"].get("functionName", "")
|
|
if function_name in escape_indicators:
|
|
info = extract_process_info(event)
|
|
findings.append({
|
|
"severity": "CRITICAL",
|
|
"function": function_name,
|
|
"binary": info["binary"],
|
|
"namespace": info["namespace"],
|
|
"pod": info["pod"],
|
|
"action": info["action"],
|
|
"description": escape_indicators[function_name]
|
|
})
|
|
return findings
|
|
|
|
|
|
def generate_event_summary(events: list[dict]) -> dict:
|
|
"""Generate a statistical summary of Tetragon events."""
|
|
event_types = Counter()
|
|
namespaces = Counter()
|
|
binaries = Counter()
|
|
actions = Counter()
|
|
|
|
for event in events:
|
|
event_type = classify_event(event)
|
|
event_types[event_type] += 1
|
|
info = extract_process_info(event)
|
|
if info["namespace"]:
|
|
namespaces[info["namespace"]] += 1
|
|
if info["binary"]:
|
|
binaries[info["binary"]] += 1
|
|
if info["action"]:
|
|
actions[info["action"]] += 1
|
|
|
|
return {
|
|
"total_events": len(events),
|
|
"event_types": dict(event_types.most_common(10)),
|
|
"top_namespaces": dict(namespaces.most_common(10)),
|
|
"top_binaries": dict(binaries.most_common(20)),
|
|
"enforcement_actions": dict(actions),
|
|
}
|
|
|
|
|
|
def generate_report(events: list[dict], output_format: str = "text") -> str:
|
|
"""Generate a comprehensive security report."""
|
|
summary = generate_event_summary(events)
|
|
suspicious = detect_suspicious_binaries(events)
|
|
priv_esc = detect_privilege_escalation(events)
|
|
escape_attempts = detect_container_escape_attempts(events)
|
|
tetragon_status = get_tetragon_status()
|
|
policies = get_tracing_policies()
|
|
|
|
if output_format == "json":
|
|
report = {
|
|
"report_generated": datetime.utcnow().isoformat(),
|
|
"tetragon_status": tetragon_status,
|
|
"tracing_policies": policies,
|
|
"event_summary": summary,
|
|
"findings": {
|
|
"suspicious_binaries": suspicious,
|
|
"privilege_escalation": priv_esc,
|
|
"container_escape_attempts": escape_attempts
|
|
},
|
|
"risk_score": calculate_risk_score(suspicious, priv_esc, escape_attempts)
|
|
}
|
|
return json.dumps(report, indent=2)
|
|
|
|
lines = []
|
|
lines.append("=" * 70)
|
|
lines.append("TETRAGON RUNTIME SECURITY REPORT")
|
|
lines.append(f"Generated: {datetime.utcnow().isoformat()}")
|
|
lines.append("=" * 70)
|
|
|
|
lines.append("\n## Tetragon Health")
|
|
lines.append(f" Status: {'HEALTHY' if tetragon_status['healthy'] else 'DEGRADED'}")
|
|
lines.append(f" Nodes: {tetragon_status['ready']}/{tetragon_status['desired']} ready")
|
|
|
|
lines.append(f"\n## TracingPolicies Deployed: {len(policies)}")
|
|
for p in policies:
|
|
lines.append(f" - {p['name']} (kprobes: {p['kprobes']}, tracepoints: {p['tracepoints']})")
|
|
|
|
lines.append(f"\n## Event Summary")
|
|
lines.append(f" Total Events: {summary['total_events']}")
|
|
lines.append(" Event Types:")
|
|
for etype, count in summary["event_types"].items():
|
|
lines.append(f" {etype}: {count}")
|
|
|
|
lines.append("\n Top Namespaces:")
|
|
for ns, count in summary["top_namespaces"].items():
|
|
lines.append(f" {ns}: {count}")
|
|
|
|
lines.append("\n Top Binaries:")
|
|
for binary, count in list(summary["top_binaries"].items())[:10]:
|
|
lines.append(f" {binary}: {count}")
|
|
|
|
risk = calculate_risk_score(suspicious, priv_esc, escape_attempts)
|
|
lines.append(f"\n## Risk Score: {risk['score']}/100 ({risk['level']})")
|
|
|
|
if escape_attempts:
|
|
lines.append(f"\n## CRITICAL: Container Escape Attempts ({len(escape_attempts)})")
|
|
for f in escape_attempts:
|
|
lines.append(f" [{f['severity']}] {f['description']}")
|
|
lines.append(f" Pod: {f['namespace']}/{f['pod']} | Binary: {f['binary']}")
|
|
|
|
if priv_esc:
|
|
lines.append(f"\n## Privilege Escalation Findings ({len(priv_esc)})")
|
|
for f in priv_esc[:20]:
|
|
lines.append(f" [{f['severity']}] {f['description']}")
|
|
|
|
if suspicious:
|
|
lines.append(f"\n## Suspicious Binary Executions ({len(suspicious)})")
|
|
for f in suspicious[:20]:
|
|
lines.append(f" [{f['severity']}] {f['description']}")
|
|
lines.append(f" Namespace: {f['namespace']} | Pod: {f['pod']}")
|
|
|
|
lines.append("\n" + "=" * 70)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def calculate_risk_score(suspicious: list, priv_esc: list, escapes: list) -> dict:
|
|
"""Calculate overall risk score based on findings."""
|
|
score = 0
|
|
score += len(escapes) * 25
|
|
score += len([f for f in priv_esc if f["severity"] == "CRITICAL"]) * 15
|
|
score += len([f for f in priv_esc if f["severity"] == "HIGH"]) * 5
|
|
score += len([f for f in suspicious if f["severity"] == "HIGH"]) * 10
|
|
score += len([f for f in suspicious if f["severity"] == "MEDIUM"]) * 3
|
|
score = min(score, 100)
|
|
|
|
if score >= 80:
|
|
level = "CRITICAL"
|
|
elif score >= 50:
|
|
level = "HIGH"
|
|
elif score >= 25:
|
|
level = "MEDIUM"
|
|
else:
|
|
level = "LOW"
|
|
|
|
return {"score": score, "level": level}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Tetragon Runtime Security Event Analyzer"
|
|
)
|
|
parser.add_argument(
|
|
"--log-file",
|
|
help="Path to Tetragon JSON event log file"
|
|
)
|
|
parser.add_argument(
|
|
"--format", choices=["text", "json"], default="text",
|
|
help="Output format (default: text)"
|
|
)
|
|
parser.add_argument(
|
|
"--status-only", action="store_true",
|
|
help="Only check Tetragon health and policy status"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.status_only:
|
|
status = get_tetragon_status()
|
|
policies = get_tracing_policies()
|
|
print(f"Tetragon Health: {'HEALTHY' if status['healthy'] else 'DEGRADED'}")
|
|
print(f"Nodes Ready: {status['ready']}/{status['desired']}")
|
|
print(f"TracingPolicies: {len(policies)}")
|
|
for p in policies:
|
|
print(f" - {p['name']}")
|
|
return
|
|
|
|
if not args.log_file:
|
|
print("[ERROR] --log-file is required (or use --status-only)")
|
|
sys.exit(1)
|
|
|
|
events = parse_tetragon_events(args.log_file)
|
|
if not events:
|
|
print("[WARN] No events found in log file")
|
|
return
|
|
|
|
report = generate_report(events, args.format)
|
|
print(report)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|