#!/usr/bin/env python3
"""Falco-based container escape detection agent.

Manages Falco rules, parses Falco alert output, and generates
escape detection reports from Falco JSON event streams.
"""

import argparse
import json
import subprocess
import sys
from datetime import datetime

ESCAPE_RULE_TAGS = ["container", "escape", "T1611", "T1610", "namespace",
                    "docker_socket", "cgroup", "kernel_module", "privileged"]

SEVERITY_MAP = {
    "Emergency": 0, "Alert": 1, "Critical": 2, "Error": 3,
    "Warning": 4, "Notice": 5, "Informational": 6, "Debug": 7
}


def check_falco_status():
    try:
        result = subprocess.run(["falco", "--version"],
                                capture_output=True, text=True, timeout=10)
        if result.returncode == 0:
            version = result.stdout.strip()
            return {"installed": True, "version": version}
    except FileNotFoundError:
        pass
    try:
        result = subprocess.run(["systemctl", "is-active", "falco"],
                                capture_output=True, text=True, timeout=5)
        return {"installed": True, "service_status": result.stdout.strip()}
    except FileNotFoundError:
        pass
    return {"installed": False}


def validate_rules_file(rules_path):
    try:
        result = subprocess.run(
            ["falco", "--validate", rules_path],
            capture_output=True, text=True, timeout=30)
        return {
            "valid": result.returncode == 0,
            "output": result.stdout.strip() if result.returncode == 0
                      else result.stderr.strip(),
        }
    except (FileNotFoundError, subprocess.TimeoutExpired) as e:
        return {"valid": False, "error": str(e)}


def parse_falco_alerts(filepath, min_priority="Warning"):
    min_level = SEVERITY_MAP.get(min_priority, 4)
    alerts = []
    escape_alerts = []

    with open(filepath, "r") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                evt = json.loads(line)
            except json.JSONDecodeError:
                continue

            priority = evt.get("priority", "Informational")
            if SEVERITY_MAP.get(priority, 6) > min_level:
                continue

            alert = {
                "time": evt.get("time", ""),
                "rule": evt.get("rule", ""),
                "priority": priority,
                "source": evt.get("source", "syscall"),
                "output": evt.get("output", ""),
                "fields": evt.get("output_fields", {}),
                "tags": evt.get("tags", []),
            }
            alerts.append(alert)

            tags = set(evt.get("tags", []))
            if tags & set(ESCAPE_RULE_TAGS):
                escape_alerts.append(alert)

    return {"total_alerts": len(alerts), "escape_alerts": escape_alerts}


def generate_escape_rules_yaml():
    rules = """# Container Escape Detection Rules for Falco
# Deploy to /etc/falco/rules.d/container-escape.yaml

- list: escape_binaries
  items: [nsenter, unshare, mount, umount, pivot_root, chroot, modprobe, insmod]

- macro: container_escape_binary
  condition: spawned_process and container and proc.name in (escape_binaries)

- rule: Container Escape Binary Execution
  desc: Detect execution of binaries commonly used for container escape
  condition: container_escape_binary
  output: "Escape binary in container (user=%user.name cmd=%proc.cmdline container=%container.name image=%container.image.repository)"
  priority: CRITICAL
  tags: [container, escape, T1611]

- rule: Docker Socket Access from Container
  desc: Container accessing Docker socket enables full host control
  condition: (open_read or open_write) and container and fd.name = /var/run/docker.sock
  output: "Docker socket accessed (user=%user.name container=%container.name cmd=%proc.cmdline)"
  priority: CRITICAL
  tags: [container, escape, docker_socket, T1610]

- rule: Cgroup Release Agent Write
  desc: Writing to cgroup release_agent is a known container escape vector
  condition: open_write and container and fd.name endswith release_agent
  output: "Cgroup escape attempt (user=%user.name container=%container.name file=%fd.name)"
  priority: CRITICAL
  tags: [container, escape, cgroup]

- rule: Kernel Module Load from Container
  desc: Container attempting to load kernel module
  condition: spawned_process and container and proc.name in (modprobe, insmod, rmmod)
  output: "Kernel module load (user=%user.name container=%container.name cmd=%proc.cmdline)"
  priority: CRITICAL
  tags: [container, escape, kernel_module]

- rule: Sensitive Proc Access from Container
  desc: Container accessing sensitive /proc or /sys paths
  condition: open_read and container and (fd.name startswith /proc/sysrq-trigger or fd.name startswith /proc/kcore or fd.name startswith /proc/kallsyms)
  output: "Sensitive proc access (container=%container.name path=%fd.name cmd=%proc.cmdline)"
  priority: CRITICAL
  tags: [container, escape, proc_access]
"""
    return rules


def main():
    parser = argparse.ArgumentParser(description="Falco Container Escape Detection")
    parser.add_argument("--check-status", action="store_true", help="Check Falco installation")
    parser.add_argument("--validate-rules", help="Validate a Falco rules file")
    parser.add_argument("--parse-alerts", help="Parse Falco JSON alert log")
    parser.add_argument("--min-priority", default="Warning",
                        choices=list(SEVERITY_MAP.keys()))
    parser.add_argument("--generate-rules", action="store_true",
                        help="Output escape detection rules YAML")
    args = parser.parse_args()

    results = {"timestamp": datetime.utcnow().isoformat() + "Z"}

    if args.check_status:
        results["falco_status"] = check_falco_status()

    if args.validate_rules:
        results["validation"] = validate_rules_file(args.validate_rules)

    if args.parse_alerts:
        parsed = parse_falco_alerts(args.parse_alerts, args.min_priority)
        results["alert_summary"] = {
            "total_alerts": parsed["total_alerts"],
            "escape_alerts_count": len(parsed["escape_alerts"]),
        }
        results["escape_alerts"] = parsed["escape_alerts"]

    if args.generate_rules:
        print(generate_escape_rules_yaml())
        return

    print(json.dumps(results, indent=2))


if __name__ == "__main__":
    main()
