Files
T

233 lines
8.3 KiB
Python

#!/usr/bin/env python3
"""
Falco Container Escape Rule Manager - Generate, validate, and deploy
custom Falco rules for container escape detection.
"""
import json
import subprocess
import sys
import argparse
from pathlib import Path
from datetime import datetime
# Container escape detection rule templates
ESCAPE_RULES = {
"mount_host_fs": {
"rule": "Container Mounting Host Filesystem",
"desc": "Detect a container attempting to mount the host filesystem",
"condition": 'spawned_process and container and proc.name = mount and (proc.args contains "/host" or proc.args contains "nsenter")',
"priority": "CRITICAL",
"tags": ["container", "escape", "T1611"],
},
"nsenter_usage": {
"rule": "Nsenter Execution in Container",
"desc": "Detect nsenter being used to escape container namespaces",
"condition": "spawned_process and container and proc.name = nsenter",
"priority": "CRITICAL",
"tags": ["container", "escape", "namespace", "T1611"],
},
"privileged_container": {
"rule": "Launch Privileged Container",
"desc": "Detect a privileged container being launched",
"condition": "container_started and container and container.privileged=true",
"priority": "WARNING",
"tags": ["container", "privileged", "T1610"],
},
"cgroup_escape": {
"rule": "Write to Cgroup Release Agent",
"desc": "Detect writes to cgroup release_agent - known escape vector",
"condition": "open_write and container and fd.name endswith release_agent",
"priority": "CRITICAL",
"tags": ["container", "escape", "cgroup", "CVE-2022-0492"],
},
"docker_socket": {
"rule": "Container Accessing Docker Socket",
"desc": "Detect container accessing Docker socket for host control",
"condition": "(open_read or open_write) and container and fd.name = /var/run/docker.sock",
"priority": "CRITICAL",
"tags": ["container", "escape", "docker-socket", "T1610"],
},
"kernel_module": {
"rule": "Container Loading Kernel Module",
"desc": "Detect a container attempting to load a kernel module",
"condition": "spawned_process and container and proc.name in (insmod, modprobe)",
"priority": "CRITICAL",
"tags": ["container", "escape", "kernel", "T1611"],
},
"shadow_file": {
"rule": "Container Reading Host Shadow File",
"desc": "Detect container reading /etc/shadow",
"condition": 'open_read and container and (fd.name = /etc/shadow or fd.name startswith /host/etc/shadow)',
"priority": "CRITICAL",
"tags": ["container", "credential-access", "T1003"],
},
"sysrq_trigger": {
"rule": "Write to Sysrq Trigger from Container",
"desc": "Detect writes to /proc/sysrq-trigger from container",
"condition": "open_write and container and fd.name = /proc/sysrq-trigger",
"priority": "CRITICAL",
"tags": ["container", "escape", "host-manipulation"],
},
}
def generate_rule_yaml(rule_key: str, rule_data: dict) -> str:
"""Generate a single Falco rule in YAML format."""
tags_str = ", ".join(rule_data["tags"])
output_fields = (
"user=%user.name container_id=%container.id "
"container_name=%container.name image=%container.image.repository "
"command=%proc.cmdline"
)
return f"""- rule: {rule_data['rule']}
desc: {rule_data['desc']}
condition: >
{rule_data['condition']}
output: >
{rule_data['desc']}
({output_fields})
priority: {rule_data['priority']}
tags: [{tags_str}]
"""
def generate_all_rules() -> str:
"""Generate complete Falco rules file for container escape detection."""
header = f"""# Container Escape Detection Rules for Falco
# Generated: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')}
# Deploy to: /etc/falco/rules.d/container-escape.yaml
- list: escape_binaries
items: [nsenter, chroot, unshare, mount, umount, pivot_root]
- macro: container_escape_binary
condition: spawned_process and container and proc.name in (escape_binaries)
"""
rules = ""
for key, data in ESCAPE_RULES.items():
rules += generate_rule_yaml(key, data) + "\n"
return header + rules
def parse_falco_alerts(log_file: str) -> list:
"""Parse Falco JSON alerts from log file."""
alerts = []
path = Path(log_file)
if not path.exists():
print(f"Log file not found: {log_file}", file=sys.stderr)
return alerts
for line in path.read_text().splitlines():
line = line.strip()
if not line:
continue
try:
alert = json.loads(line)
alerts.append({
"time": alert.get("time", ""),
"rule": alert.get("rule", ""),
"priority": alert.get("priority", ""),
"output": alert.get("output", ""),
"tags": alert.get("tags", []),
"container_name": alert.get("output_fields", {}).get("container.name", ""),
"container_image": alert.get("output_fields", {}).get("container.image.repository", ""),
})
except json.JSONDecodeError:
continue
return alerts
def summarize_alerts(alerts: list) -> dict:
"""Summarize Falco alerts by rule and severity."""
summary = {"total": len(alerts), "by_priority": {}, "by_rule": {}, "escape_attempts": []}
for alert in alerts:
pri = alert["priority"]
rule = alert["rule"]
summary["by_priority"][pri] = summary["by_priority"].get(pri, 0) + 1
summary["by_rule"][rule] = summary["by_rule"].get(rule, 0) + 1
if any(tag in alert.get("tags", []) for tag in ["escape", "T1611", "T1610"]):
summary["escape_attempts"].append(alert)
return summary
def check_falco_health() -> dict:
"""Check if Falco is running and healthy."""
result = subprocess.run(
["kubectl", "get", "pods", "-n", "falco", "-l", "app.kubernetes.io/name=falco",
"-o", "json"],
capture_output=True, text=True,
)
if result.returncode != 0:
return {"healthy": False, "error": result.stderr}
pods = json.loads(result.stdout)
pod_status = []
for pod in pods.get("items", []):
name = pod["metadata"]["name"]
phase = pod["status"]["phase"]
node = pod["spec"].get("nodeName", "unknown")
pod_status.append({"name": name, "phase": phase, "node": node})
all_running = all(p["phase"] == "Running" for p in pod_status)
return {"healthy": all_running, "pods": pod_status}
def main():
parser = argparse.ArgumentParser(description="Falco Container Escape Rule Manager")
subparsers = parser.add_subparsers(dest="command")
subparsers.add_parser("generate", help="Generate container escape detection rules")
parse_cmd = subparsers.add_parser("parse-alerts", help="Parse Falco alert logs")
parse_cmd.add_argument("--log-file", required=True, help="Path to Falco JSON log file")
subparsers.add_parser("health", help="Check Falco deployment health")
deploy_cmd = subparsers.add_parser("deploy", help="Deploy rules to Kubernetes")
deploy_cmd.add_argument("--namespace", default="falco", help="Falco namespace")
args = parser.parse_args()
if args.command == "generate":
print(generate_all_rules())
elif args.command == "parse-alerts":
alerts = parse_falco_alerts(args.log_file)
summary = summarize_alerts(alerts)
print(json.dumps(summary, indent=2))
elif args.command == "health":
health = check_falco_health()
print(json.dumps(health, indent=2))
elif args.command == "deploy":
rules_yaml = generate_all_rules()
proc = subprocess.run(
["kubectl", "create", "configmap", "falco-escape-rules",
"-n", args.namespace, "--from-literal=container-escape.yaml=" + rules_yaml,
"--dry-run=client", "-o", "yaml"],
capture_output=True, text=True,
)
apply = subprocess.run(
["kubectl", "apply", "-f", "-"],
input=proc.stdout, capture_output=True, text=True,
)
print(apply.stdout)
if apply.returncode == 0:
print("Rules deployed. Restart Falco to load:")
print(f" kubectl rollout restart daemonset/falco -n {args.namespace}")
else:
parser.print_help()
if __name__ == "__main__":
main()