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

286 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Kubernetes RBAC Auditor
Analyzes Kubernetes RBAC configurations to identify overly permissive
roles, dangerous permissions, unnecessary ClusterRoleBindings, and
service account security issues.
"""
import json
import datetime
from typing import Dict, List, Set
from dataclasses import dataclass, field
@dataclass
class K8sRole:
"""Kubernetes Role or ClusterRole."""
name: str
namespace: str # empty for ClusterRole
is_cluster_role: bool
rules: List[Dict] = field(default_factory=list)
# Each rule: {"apiGroups": [...], "resources": [...], "verbs": [...]}
@dataclass
class K8sBinding:
"""Kubernetes RoleBinding or ClusterRoleBinding."""
name: str
namespace: str
is_cluster_binding: bool
role_ref: str
role_ref_kind: str # Role or ClusterRole
subjects: List[Dict] = field(default_factory=list)
# Each subject: {"kind": "User/Group/ServiceAccount", "name": "...", "namespace": "..."}
@dataclass
class RBACFinding:
severity: str
category: str
title: str
description: str
recommendation: str = ""
affected_resources: List[str] = field(default_factory=list)
class KubernetesRBACAuditor:
"""Audits Kubernetes RBAC for security issues."""
DANGEROUS_VERBS = {"*", "escalate", "bind", "impersonate"}
SENSITIVE_RESOURCES = {"secrets", "roles", "clusterroles", "rolebindings",
"clusterrolebindings", "nodes", "persistentvolumes"}
EXEC_RESOURCES = {"pods/exec", "pods/attach"}
def __init__(self):
self.roles: List[K8sRole] = []
self.bindings: List[K8sBinding] = []
self.findings: List[RBACFinding] = []
def load_roles(self, roles: List[Dict]):
for r in roles:
self.roles.append(K8sRole(**r))
def load_bindings(self, bindings: List[Dict]):
for b in bindings:
self.bindings.append(K8sBinding(**b))
def audit_all(self) -> List[RBACFinding]:
self.findings = []
self._audit_wildcard_permissions()
self._audit_dangerous_verbs()
self._audit_cluster_admin_bindings()
self._audit_service_account_bindings()
self._audit_exec_permissions()
self._audit_secret_access()
self._audit_rbac_modification_permissions()
return self.findings
def _audit_wildcard_permissions(self):
for role in self.roles:
for rule in role.rules:
if "*" in rule.get("resources", []) or "*" in rule.get("verbs", []):
scope = "ClusterRole" if role.is_cluster_role else f"Role in {role.namespace}"
self.findings.append(RBACFinding(
severity="critical",
category="Wildcard Permissions",
title=f"Wildcard permissions in {scope} '{role.name}'",
description=f"Resources: {rule.get('resources')}, Verbs: {rule.get('verbs')}. "
"Wildcard grants excessive access violating least privilege.",
recommendation="Replace wildcards with explicit resource and verb lists.",
affected_resources=[role.name]
))
def _audit_dangerous_verbs(self):
for role in self.roles:
for rule in role.rules:
dangerous = set(rule.get("verbs", [])) & self.DANGEROUS_VERBS
if dangerous and "*" not in dangerous: # wildcard already caught
self.findings.append(RBACFinding(
severity="critical",
category="Dangerous Verbs",
title=f"Dangerous verbs in '{role.name}': {', '.join(dangerous)}",
description="escalate/bind allow privilege escalation. "
"impersonate allows identity spoofing.",
recommendation="Remove dangerous verbs. Only cluster-admin should have these.",
affected_resources=[role.name]
))
def _audit_cluster_admin_bindings(self):
cluster_admin_bindings = [
b for b in self.bindings
if b.role_ref == "cluster-admin" and b.is_cluster_binding
]
for binding in cluster_admin_bindings:
for subject in binding.subjects:
if subject.get("kind") == "ServiceAccount":
self.findings.append(RBACFinding(
severity="critical",
category="Cluster Admin",
title=f"ServiceAccount bound to cluster-admin: {subject.get('name')}",
description=f"Service account '{subject.get('name')}' in namespace "
f"'{subject.get('namespace', 'default')}' has full cluster admin access.",
recommendation="Create a dedicated ClusterRole with minimum required permissions.",
affected_resources=[binding.name]
))
elif subject.get("kind") == "Group" and subject.get("name") not in (
"system:masters",
):
self.findings.append(RBACFinding(
severity="high",
category="Cluster Admin",
title=f"Group bound to cluster-admin: {subject.get('name')}",
description=f"All members of group '{subject.get('name')}' have full cluster admin.",
recommendation="Review group membership. Use namespace-scoped roles instead.",
affected_resources=[binding.name]
))
def _audit_service_account_bindings(self):
default_sa_bindings = []
for binding in self.bindings:
for subject in binding.subjects:
if (subject.get("kind") == "ServiceAccount" and
subject.get("name") == "default"):
default_sa_bindings.append(binding)
if default_sa_bindings:
self.findings.append(RBACFinding(
severity="high",
category="Service Account",
title=f"Default service account has {len(default_sa_bindings)} custom bindings",
description="Default service account should not have additional permissions. "
"All pods without explicit SA use the default SA.",
recommendation="Create dedicated service accounts per application. "
"Remove bindings from default SA.",
affected_resources=[b.name for b in default_sa_bindings]
))
def _audit_exec_permissions(self):
for role in self.roles:
for rule in role.rules:
resources = set(rule.get("resources", []))
exec_resources = resources & self.EXEC_RESOURCES
if exec_resources:
self.findings.append(RBACFinding(
severity="high",
category="Pod Exec",
title=f"Pod exec/attach permission in '{role.name}'",
description="pods/exec allows running commands inside containers. "
"This can be used for lateral movement.",
recommendation="Restrict exec access to debugging roles. "
"Monitor exec usage in audit logs.",
affected_resources=[role.name]
))
def _audit_secret_access(self):
for role in self.roles:
for rule in role.rules:
resources = set(rule.get("resources", []))
verbs = set(rule.get("verbs", []))
if "secrets" in resources:
write_verbs = verbs & {"create", "update", "patch", "delete", "*"}
if write_verbs:
self.findings.append(RBACFinding(
severity="high",
category="Secret Access",
title=f"Secret write access in '{role.name}'",
description=f"Write verbs on secrets: {', '.join(write_verbs)}. "
"This allows creating/modifying secrets.",
recommendation="Limit secret write access to operators and CI/CD only.",
affected_resources=[role.name]
))
def _audit_rbac_modification_permissions(self):
rbac_resources = {"roles", "clusterroles", "rolebindings", "clusterrolebindings"}
for role in self.roles:
if role.name in ("cluster-admin", "admin"):
continue # Skip built-in roles
for rule in role.rules:
resources = set(rule.get("resources", []))
if resources & rbac_resources:
verbs = set(rule.get("verbs", []))
write_verbs = verbs & {"create", "update", "patch", "delete", "*"}
if write_verbs:
self.findings.append(RBACFinding(
severity="critical",
category="RBAC Modification",
title=f"RBAC modification permissions in '{role.name}'",
description=f"Can modify RBAC objects: {resources & rbac_resources}. "
"This enables privilege escalation.",
recommendation="Remove RBAC modification permissions from non-admin roles.",
affected_resources=[role.name]
))
def generate_report(self) -> str:
if not self.findings:
self.audit_all()
lines = [
"=" * 70,
"KUBERNETES RBAC AUDIT REPORT",
"=" * 70,
f"Report Date: {datetime.datetime.now().isoformat()}",
f"Roles/ClusterRoles Audited: {len(self.roles)}",
f"Bindings Audited: {len(self.bindings)}",
f"Findings: {len(self.findings)}",
"-" * 70, ""
]
severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
for f in sorted(self.findings, key=lambda x: severity_order.get(x.severity, 5)):
lines.append(f"[{f.severity.upper()}] {f.title}")
lines.append(f" Category: {f.category}")
lines.append(f" {f.description}")
if f.recommendation:
lines.append(f" Fix: {f.recommendation}")
if f.affected_resources:
lines.append(f" Affected: {', '.join(f.affected_resources)}")
lines.append("")
critical = sum(1 for f in self.findings if f.severity == "critical")
lines.append("=" * 70)
lines.append(f"OVERALL: {'FAIL' if critical else 'PASS'}")
lines.append("=" * 70)
return "\n".join(lines)
def main():
auditor = KubernetesRBACAuditor()
auditor.load_roles([
{"name": "developer", "namespace": "app-team", "is_cluster_role": False,
"rules": [
{"apiGroups": ["", "apps"], "resources": ["pods", "deployments", "services"], "verbs": ["get", "list", "create", "update", "delete"]},
{"apiGroups": [""], "resources": ["secrets"], "verbs": ["get", "list"]},
{"apiGroups": [""], "resources": ["pods/exec"], "verbs": ["create"]}
]},
{"name": "ci-deployer", "namespace": "", "is_cluster_role": True,
"rules": [
{"apiGroups": ["*"], "resources": ["*"], "verbs": ["*"]}
]},
{"name": "custom-admin", "namespace": "production", "is_cluster_role": False,
"rules": [
{"apiGroups": ["rbac.authorization.k8s.io"], "resources": ["roles", "rolebindings"], "verbs": ["create", "update", "delete"]},
{"apiGroups": [""], "resources": ["secrets"], "verbs": ["create", "update", "delete"]}
]},
])
auditor.load_bindings([
{"name": "ci-deployer-binding", "namespace": "", "is_cluster_binding": True,
"role_ref": "cluster-admin", "role_ref_kind": "ClusterRole",
"subjects": [{"kind": "ServiceAccount", "name": "ci-deployer", "namespace": "ci-cd"}]},
{"name": "dev-binding", "namespace": "app-team", "is_cluster_binding": False,
"role_ref": "developer", "role_ref_kind": "Role",
"subjects": [{"kind": "Group", "name": "dev-team"}]},
{"name": "default-elevated", "namespace": "app-team", "is_cluster_binding": False,
"role_ref": "developer", "role_ref_kind": "Role",
"subjects": [{"kind": "ServiceAccount", "name": "default", "namespace": "app-team"}]},
])
print(auditor.generate_report())
if __name__ == "__main__":
main()