Files
Anthropic-Cybersecurity-Skills/skills/auditing-kubernetes-rbac-permissions/scripts/process.py
T

258 lines
9.4 KiB
Python

#!/usr/bin/env python3
"""
Kubernetes RBAC Permissions Auditor
Audits RBAC configurations for overly permissive roles,
dangerous permission combinations, and privilege escalation paths.
"""
import subprocess
import json
import sys
from dataclasses import dataclass, field
DANGEROUS_VERBS = {"*", "escalate", "bind", "impersonate"}
DANGEROUS_RESOURCES = {"*", "secrets", "pods", "clusterroles", "clusterrolebindings", "roles", "rolebindings"}
HIGH_RISK_COMBINATIONS = [
({"*"}, {"*"}, "CRITICAL", "Wildcard access on all resources (cluster-admin equivalent)"),
({"create", "update", "patch"}, {"clusterrolebindings", "rolebindings"}, "CRITICAL", "Can create role bindings for privilege escalation"),
({"escalate"}, {"clusterroles", "roles"}, "CRITICAL", "Can escalate role permissions beyond own level"),
({"impersonate"}, {"users", "groups", "serviceaccounts"}, "CRITICAL", "Can impersonate any identity"),
({"get", "list", "watch"}, {"secrets"}, "HIGH", "Can read all secrets in scope"),
({"create"}, {"pods"}, "HIGH", "Can create pods (deploy workloads)"),
({"create"}, {"pods/exec"}, "HIGH", "Can exec into pods (command execution)"),
({"delete"}, {"pods", "nodes", "namespaces"}, "HIGH", "Can delete critical resources"),
]
@dataclass
class RBACFinding:
resource_type: str
resource_name: str
namespace: str
severity: str
issue: str
details: str
remediation: str
@dataclass
class RBACAuditReport:
findings: list = field(default_factory=list)
cluster_roles: int = 0
roles: int = 0
cluster_role_bindings: int = 0
role_bindings: int = 0
service_accounts: int = 0
def run_kubectl_json(args: list):
cmd = ["kubectl"] + args + ["-o", "json"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
return None
return json.loads(result.stdout)
except (subprocess.TimeoutExpired, json.JSONDecodeError, FileNotFoundError):
return None
def check_role_rules(rules: list, role_name: str, role_type: str, namespace: str, report: RBACAuditReport):
"""Analyze role rules for dangerous permissions."""
for rule in rules:
verbs = set(rule.get("verbs", []))
resources = set(rule.get("resources", []))
api_groups = rule.get("apiGroups", [])
for req_verbs, req_resources, severity, description in HIGH_RISK_COMBINATIONS:
verb_match = "*" in verbs or bool(verbs & req_verbs)
resource_match = "*" in resources or bool(resources & req_resources)
if verb_match and resource_match:
report.findings.append(RBACFinding(
resource_type=role_type,
resource_name=role_name,
namespace=namespace,
severity=severity,
issue=description,
details=f"verbs={list(verbs)}, resources={list(resources)}, apiGroups={api_groups}",
remediation=f"Restrict {role_type} '{role_name}' to minimum required permissions"
))
break
def audit_cluster_roles(report: RBACAuditReport):
"""Audit all ClusterRoles."""
print("[*] Auditing ClusterRoles...")
data = run_kubectl_json(["get", "clusterroles"])
if not data:
return
items = data.get("items", [])
report.cluster_roles = len(items)
for cr in items:
name = cr["metadata"]["name"]
# Skip well-known system roles
if name.startswith("system:") and name not in ("system:aggregate-to-admin", "system:aggregate-to-edit"):
continue
rules = cr.get("rules", [])
check_role_rules(rules, name, "ClusterRole", "cluster-wide", report)
def audit_roles(report: RBACAuditReport):
"""Audit all namespace Roles."""
print("[*] Auditing Roles...")
data = run_kubectl_json(["get", "roles", "-A"])
if not data:
return
items = data.get("items", [])
report.roles = len(items)
for role in items:
name = role["metadata"]["name"]
namespace = role["metadata"]["namespace"]
rules = role.get("rules", [])
check_role_rules(rules, name, "Role", namespace, report)
def audit_bindings(report: RBACAuditReport):
"""Audit ClusterRoleBindings for dangerous subject assignments."""
print("[*] Auditing ClusterRoleBindings...")
data = run_kubectl_json(["get", "clusterrolebindings"])
if not data:
return
items = data.get("items", [])
report.cluster_role_bindings = len(items)
dangerous_subjects = {"system:anonymous", "system:unauthenticated"}
admin_roles = {"cluster-admin", "admin", "edit"}
for crb in items:
name = crb["metadata"]["name"]
role_ref = crb.get("roleRef", {}).get("name", "")
subjects = crb.get("subjects", []) or []
for subject in subjects:
s_name = subject.get("name", "")
s_kind = subject.get("kind", "")
if s_name in dangerous_subjects and role_ref in admin_roles:
report.findings.append(RBACFinding(
resource_type="ClusterRoleBinding",
resource_name=name,
namespace="cluster-wide",
severity="CRITICAL",
issue=f"Dangerous subject '{s_name}' bound to '{role_ref}'",
details=f"Subject {s_kind}/{s_name} has {role_ref} access",
remediation=f"Remove or restrict ClusterRoleBinding '{name}'"
))
# Check for system:authenticated bound to admin roles
if s_name == "system:authenticated" and role_ref in admin_roles:
report.findings.append(RBACFinding(
resource_type="ClusterRoleBinding",
resource_name=name,
namespace="cluster-wide",
severity="CRITICAL",
issue=f"All authenticated users have '{role_ref}' access",
details=f"Group system:authenticated bound to {role_ref}",
remediation=f"Remove binding, use specific user/group bindings"
))
def audit_service_accounts(report: RBACAuditReport):
"""Audit service accounts for over-permissioning."""
print("[*] Auditing Service Accounts...")
data = run_kubectl_json(["get", "serviceaccounts", "-A"])
if not data:
return
items = data.get("items", [])
report.service_accounts = len(items)
# Check default SAs that have non-default bindings
crbs = run_kubectl_json(["get", "clusterrolebindings"])
rbs = run_kubectl_json(["get", "rolebindings", "-A"])
if crbs:
for crb in crbs.get("items", []):
for subject in crb.get("subjects", []) or []:
if subject.get("kind") == "ServiceAccount" and subject.get("name") == "default":
report.findings.append(RBACFinding(
resource_type="ServiceAccount",
resource_name=f"default ({subject.get('namespace', 'unknown')})",
namespace=subject.get("namespace", "unknown"),
severity="HIGH",
issue=f"Default SA bound to ClusterRole '{crb['roleRef']['name']}'",
details="Default service account should not have additional permissions",
remediation="Create dedicated service account, remove default SA binding"
))
def print_report(report: RBACAuditReport):
print("\n" + "=" * 70)
print("KUBERNETES RBAC AUDIT REPORT")
print("=" * 70)
print(f"ClusterRoles: {report.cluster_roles}")
print(f"Roles: {report.roles}")
print(f"ClusterRoleBindings: {report.cluster_role_bindings}")
print(f"RoleBindings: {report.role_bindings}")
print(f"ServiceAccounts: {report.service_accounts}")
print(f"Total Findings: {len(report.findings)}")
print("=" * 70)
for severity in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]:
findings = [f for f in report.findings if f.severity == severity]
if findings:
print(f"\n{severity} ({len(findings)}):")
print("-" * 70)
for f in findings:
print(f" [{f.resource_type}] {f.resource_name}")
print(f" Issue: {f.issue}")
print(f" Details: {f.details}")
print(f" Fix: {f.remediation}")
print()
def main():
print("[*] Kubernetes RBAC Permissions Auditor\n")
report = RBACAuditReport()
audit_cluster_roles(report)
audit_roles(report)
audit_bindings(report)
audit_service_accounts(report)
print_report(report)
output = {
"summary": {
"cluster_roles": report.cluster_roles,
"roles": report.roles,
"findings": len(report.findings),
},
"findings": [
{"type": f.resource_type, "name": f.resource_name, "namespace": f.namespace,
"severity": f.severity, "issue": f.issue, "remediation": f.remediation}
for f in report.findings
],
}
with open("rbac_audit_report.json", "w") as f:
json.dump(output, f, indent=2)
print("[*] Report saved to rbac_audit_report.json")
critical = sum(1 for f in report.findings if f.severity == "CRITICAL")
if critical > 0:
print(f"\n[!] {critical} CRITICAL findings found")
sys.exit(1)
if __name__ == "__main__":
main()