Files
Anthropic-Cybersecurity-Skills/skills/implementing-rbac-hardening-for-kubernetes/scripts/process.py
T

194 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""
Kubernetes RBAC Audit and Hardening Tool
Audits RBAC configurations to identify overprivileged accounts,
cluster-admin sprawl, default service account usage, and
generates hardening recommendations.
"""
import json
import subprocess
import sys
import argparse
from datetime import datetime
from collections import defaultdict
DANGEROUS_VERBS = {"*", "create", "update", "patch", "delete"}
DANGEROUS_RESOURCES = {
"secrets", "pods/exec", "clusterroles", "clusterrolebindings",
"roles", "rolebindings", "serviceaccounts/token", "nodes/proxy"
}
DANGEROUS_API_GROUPS = {"*"}
def run_kubectl(args: list[str]) -> str:
try:
result = subprocess.run(
["kubectl"] + args, capture_output=True, text=True, timeout=30
)
return result.stdout.strip()
except (subprocess.TimeoutExpired, FileNotFoundError):
return ""
def get_cluster_role_bindings() -> list[dict]:
output = run_kubectl(["get", "clusterrolebindings", "-o", "json"])
if not output:
return []
try:
return json.loads(output).get("items", [])
except json.JSONDecodeError:
return []
def get_role_bindings() -> list[dict]:
output = run_kubectl(["get", "rolebindings", "--all-namespaces", "-o", "json"])
if not output:
return []
try:
return json.loads(output).get("items", [])
except json.JSONDecodeError:
return []
def get_cluster_roles() -> dict:
output = run_kubectl(["get", "clusterroles", "-o", "json"])
if not output:
return {}
try:
items = json.loads(output).get("items", [])
return {item["metadata"]["name"]: item.get("rules", []) for item in items}
except json.JSONDecodeError:
return {}
def audit_cluster_admin_bindings(crbs: list[dict]) -> list[dict]:
findings = []
for crb in crbs:
if crb.get("roleRef", {}).get("name") == "cluster-admin":
for subject in crb.get("subjects", []):
findings.append({
"severity": "CRITICAL",
"type": "cluster_admin_binding",
"binding": crb["metadata"]["name"],
"subject_kind": subject.get("kind", ""),
"subject_name": subject.get("name", ""),
"subject_namespace": subject.get("namespace", ""),
"description": f"cluster-admin bound to {subject.get('kind', '')}/{subject.get('name', '')}"
})
return findings
def audit_wildcard_permissions(roles: dict) -> list[dict]:
findings = []
for role_name, rules in roles.items():
for rule in rules:
verbs = rule.get("verbs", [])
resources = rule.get("resources", [])
api_groups = rule.get("apiGroups", [])
if "*" in verbs and "*" in resources:
findings.append({
"severity": "HIGH",
"type": "wildcard_permissions",
"role": role_name,
"description": f"ClusterRole {role_name} has wildcard verbs and resources"
})
elif "*" in verbs:
findings.append({
"severity": "MEDIUM",
"type": "wildcard_verbs",
"role": role_name,
"resources": resources,
"description": f"ClusterRole {role_name} has wildcard verbs on {resources}"
})
return findings
def audit_dangerous_permissions(roles: dict) -> list[dict]:
findings = []
for role_name, rules in roles.items():
for rule in rules:
verbs = set(rule.get("verbs", []))
resources = set(rule.get("resources", []))
dangerous_matches = resources.intersection(DANGEROUS_RESOURCES)
has_dangerous_verbs = verbs.intersection(DANGEROUS_VERBS)
if dangerous_matches and has_dangerous_verbs:
findings.append({
"severity": "HIGH",
"type": "dangerous_permission",
"role": role_name,
"resources": list(dangerous_matches),
"verbs": list(has_dangerous_verbs),
"description": f"ClusterRole {role_name} grants {list(has_dangerous_verbs)} on {list(dangerous_matches)}"
})
return findings
def audit_default_service_accounts(rbs: list[dict], crbs: list[dict]) -> list[dict]:
findings = []
for binding in rbs + crbs:
for subject in binding.get("subjects", []):
if subject.get("kind") == "ServiceAccount" and subject.get("name") == "default":
findings.append({
"severity": "MEDIUM",
"type": "default_sa_binding",
"binding": binding["metadata"]["name"],
"namespace": subject.get("namespace", "N/A"),
"role": binding.get("roleRef", {}).get("name", ""),
"description": f"Default service account in {subject.get('namespace', 'N/A')} has role binding"
})
return findings
def generate_report(all_findings: list[dict], output_format: str = "text") -> str:
critical = [f for f in all_findings if f["severity"] == "CRITICAL"]
high = [f for f in all_findings if f["severity"] == "HIGH"]
medium = [f for f in all_findings if f["severity"] == "MEDIUM"]
if output_format == "json":
return json.dumps({
"timestamp": datetime.utcnow().isoformat(),
"summary": {"critical": len(critical), "high": len(high), "medium": len(medium)},
"findings": all_findings
}, indent=2)
lines = ["=" * 70, "KUBERNETES RBAC HARDENING AUDIT REPORT",
f"Generated: {datetime.utcnow().isoformat()}", "=" * 70]
lines.append(f"\nFindings: {len(critical)} Critical, {len(high)} High, {len(medium)} Medium")
for sev, items in [("CRITICAL", critical), ("HIGH", high), ("MEDIUM", medium)]:
if items:
lines.append(f"\n## {sev}")
for f in items:
lines.append(f" [{f['type']}] {f['description']}")
lines.append("\n" + "=" * 70)
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Kubernetes RBAC Audit Tool")
parser.add_argument("--format", choices=["text", "json"], default="text")
args = parser.parse_args()
crbs = get_cluster_role_bindings()
rbs = get_role_bindings()
roles = get_cluster_roles()
all_findings = []
all_findings.extend(audit_cluster_admin_bindings(crbs))
all_findings.extend(audit_wildcard_permissions(roles))
all_findings.extend(audit_dangerous_permissions(roles))
all_findings.extend(audit_default_service_accounts(rbs, crbs))
print(generate_report(all_findings, args.format))
sys.exit(1 if any(f["severity"] == "CRITICAL" for f in all_findings) else 0)
if __name__ == "__main__":
main()