npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
Kubernetes RBAC regulates access to cluster resources based on roles assigned to users, groups, and service accounts. Default configurations often grant excessive permissions, and without active hardening, RBAC becomes a primary attack vector for privilege escalation, lateral movement, and data exfiltration. Hardening requires implementing least-privilege principles, eliminating unnecessary ClusterRole bindings, separating service accounts, integrating external identity providers, and continuous auditing.
When to Use
- When deploying or configuring implementing rbac hardening for kubernetes capabilities in your environment
- When establishing security controls aligned to compliance requirements
- When building or improving security architecture for this domain
- When conducting security assessments that require this implementation
Prerequisites
- Kubernetes cluster v1.24+ with RBAC enabled (default since v1.6)
- kubectl access with cluster-admin for initial audit
- External identity provider (OIDC) for user authentication
- Audit logging enabled on the API server
Core Hardening Principles
1. Eliminate cluster-admin Sprawl
Audit and remove unnecessary cluster-admin bindings:
# List all cluster-admin bindings
kubectl get clusterrolebindings -o json | jq -r '
.items[] |
select(.roleRef.name == "cluster-admin") |
"\(.metadata.name) -> \(.subjects[]? | "\(.kind)/\(.name) (\(.namespace // "cluster"))")"
'2. Namespace-Scoped Roles Over ClusterRoles
Use Role and RoleBinding instead of ClusterRole and ClusterRoleBinding:
# Good: Namespace-scoped role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: application
name: app-developer
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: application
name: app-developer-binding
subjects:
- kind: Group
name: dev-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: app-developer
apiGroup: rbac.authorization.k8s.io3. Dedicated Service Accounts Per Workload
apiVersion: v1
kind: ServiceAccount
metadata:
name: payment-processor
namespace: payments
automountServiceAccountToken: false # Disable auto-mount
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-processor
namespace: payments
spec:
template:
spec:
serviceAccountName: payment-processor
automountServiceAccountToken: true # Only mount when explicitly needed
containers:
- name: processor
image: payments/processor:v2.1@sha256:abc...4. Restrict Dangerous Permissions
Block permissions that enable privilege escalation:
# Dangerous verbs/resources to restrict:
# - secrets: get, list, watch (exposes all secrets in namespace)
# - pods/exec: create (enables command execution in pods)
# - pods: create with privileged securityContext
# - serviceaccounts/token: create (generates new tokens)
# - clusterroles/clusterrolebindings: create, update (self-escalation)
# - nodes/proxy: create (bypasses API server authorization)
# Safe read-only role example
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: security-viewer
rules:
- apiGroups: [""]
resources: ["pods", "services", "namespaces", "nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "daemonsets", "statefulsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["get", "list", "watch"]5. OIDC Integration for User Authentication
# API server flags for OIDC integration
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
- --oidc-issuer-url=https://idp.company.com
- --oidc-client-id=kubernetes
- --oidc-username-claim=email
- --oidc-groups-claim=groups
- --oidc-ca-file=/etc/kubernetes/pki/oidc-ca.crtRBAC Audit Process
Step 1: Enumerate All Bindings
# All ClusterRoleBindings with subjects
kubectl get clusterrolebindings -o json | jq -r '
.items[] | select(.subjects != null) |
.subjects[] as $s |
"\(.metadata.name) | \(.roleRef.name) | \($s.kind)/\($s.name)"
' | sort | column -t -s '|'
# All RoleBindings across namespaces
kubectl get rolebindings --all-namespaces -o json | jq -r '
.items[] | select(.subjects != null) |
.subjects[] as $s |
"\(.metadata.namespace) | \(.metadata.name) | \(.roleRef.name) | \($s.kind)/\($s.name)"
' | sort | column -t -s '|'Step 2: Identify Overprivileged Service Accounts
# Find service accounts with cluster-admin or admin roles
kubectl get clusterrolebindings -o json | jq -r '
.items[] |
select(.roleRef.name == "cluster-admin" or .roleRef.name == "admin") |
select(.subjects[]?.kind == "ServiceAccount") |
"\(.subjects[] | select(.kind == "ServiceAccount") | "\(.namespace)/\(.name)")"
'Step 3: Check Default Service Account Usage
# Find pods using the default service account
kubectl get pods --all-namespaces -o json | jq -r '
.items[] |
select(.spec.serviceAccountName == "default" or .spec.serviceAccountName == null) |
"\(.metadata.namespace)/\(.metadata.name)"
'Step 4: Verify Token Auto-Mount
# Find pods with auto-mounted service account tokens
kubectl get pods --all-namespaces -o json | jq -r '
.items[] |
select(.spec.automountServiceAccountToken != false) |
"\(.metadata.namespace)/\(.metadata.name) sa=\(.spec.serviceAccountName // "default")"
'Tooling
rbac-lookup
# Install rbac-lookup
kubectl krew install rbac-lookup
# View RBAC for a specific user
kubectl rbac-lookup developer@company.com
# View all RBAC bindings wide format
kubectl rbac-lookup --kind user -o widerakkess (Review Access)
# Install rakkess
kubectl krew install access-matrix
# Show access matrix for current user
kubectl access-matrix
# Show access for a specific service account
kubectl access-matrix --sa payments:payment-processorReferences
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md6.1 KB
API Reference: Kubernetes RBAC Hardening Audit
Libraries Used
| Library | Purpose |
|---|---|
kubernetes |
Official Kubernetes Python client for RBAC API |
json |
Parse and format RBAC audit results |
yaml |
Read Kubernetes RBAC manifest files |
Installation
pip install kubernetes pyyamlAuthentication
from kubernetes import client, config
# Local kubeconfig
config.load_kube_config()
# In-cluster
# config.load_incluster_config()
rbac_api = client.RbacAuthorizationV1Api()
core_api = client.CoreV1Api()RBAC API Methods
| Method | Description |
|---|---|
list_cluster_role() |
List all ClusterRoles |
list_cluster_role_binding() |
List all ClusterRoleBindings |
list_namespaced_role(namespace) |
List Roles in a namespace |
list_namespaced_role_binding(namespace) |
List RoleBindings in a namespace |
read_cluster_role(name) |
Get specific ClusterRole details |
read_cluster_role_binding(name) |
Get specific ClusterRoleBinding |
Core Audit Operations
Detect Wildcard Permissions
def find_wildcard_permissions():
"""Find ClusterRoles with wildcard (*) verbs, resources, or apiGroups."""
findings = []
roles = rbac_api.list_cluster_role()
for role in roles.items:
if not role.rules:
continue
for rule in role.rules:
wildcards = []
if rule.verbs and "*" in rule.verbs:
wildcards.append("verbs")
if rule.resources and "*" in rule.resources:
wildcards.append("resources")
if rule.api_groups and "*" in rule.api_groups:
wildcards.append("apiGroups")
if wildcards:
findings.append({
"role": role.metadata.name,
"wildcards": wildcards,
"severity": "critical" if len(wildcards) >= 2 else "high",
})
return findingsFind Subjects Bound to cluster-admin
def find_cluster_admin_bindings():
"""Identify all subjects with cluster-admin privileges."""
bindings = rbac_api.list_cluster_role_binding()
admin_subjects = []
for binding in bindings.items:
if binding.role_ref.name == "cluster-admin":
for subject in binding.subjects or []:
admin_subjects.append({
"binding": binding.metadata.name,
"subject_kind": subject.kind,
"subject_name": subject.name,
"namespace": subject.namespace or "cluster-wide",
"severity": "high",
})
return admin_subjectsDetect Privilege Escalation Risks
ESCALATION_VERBS = {"bind", "escalate", "impersonate"}
DANGEROUS_RESOURCES = {"secrets", "pods/exec", "serviceaccounts/token"}
def find_escalation_risks():
findings = []
roles = rbac_api.list_cluster_role()
for role in roles.items:
for rule in (role.rules or []):
dangerous_verbs = set(rule.verbs or []) & ESCALATION_VERBS
dangerous_resources = set(rule.resources or []) & DANGEROUS_RESOURCES
if dangerous_verbs:
findings.append({
"role": role.metadata.name,
"issue": f"Escalation verbs: {dangerous_verbs}",
"severity": "critical",
})
if dangerous_resources and "get" in (rule.verbs or []):
findings.append({
"role": role.metadata.name,
"issue": f"Access to sensitive resources: {dangerous_resources}",
"severity": "high",
})
return findingsAudit Service Account Token Auto-Mount
def find_automount_service_tokens():
"""Find pods with automountServiceAccountToken enabled."""
findings = []
namespaces = core_api.list_namespace()
for ns in namespaces.items:
pods = core_api.list_namespaced_pod(ns.metadata.name)
for pod in pods.items:
automount = pod.spec.automount_service_account_token
if automount is None or automount is True:
sa = pod.spec.service_account_name or "default"
if sa != "default":
findings.append({
"namespace": ns.metadata.name,
"pod": pod.metadata.name,
"service_account": sa,
"issue": "automountServiceAccountToken not disabled",
})
return findingsFind Unused Roles
def find_unused_roles():
"""Detect Roles with no corresponding RoleBindings."""
namespaces = core_api.list_namespace()
unused = []
for ns in namespaces.items:
roles = rbac_api.list_namespaced_role(ns.metadata.name)
bindings = rbac_api.list_namespaced_role_binding(ns.metadata.name)
bound_roles = {b.role_ref.name for b in bindings.items}
for role in roles.items:
if role.metadata.name not in bound_roles:
unused.append({
"namespace": ns.metadata.name,
"role": role.metadata.name,
"issue": "Role has no bindings — candidate for removal",
})
return unusedkubectl Equivalents
# List all ClusterRoleBindings for cluster-admin
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects[]'
# Find roles with wildcard permissions
kubectl get clusterroles -o json | \
jq '.items[] | select(.rules[]?.verbs[]? == "*") | .metadata.name'
# Audit RBAC with rakkess (kubectl plugin)
kubectl krew install access-matrix
kubectl access-matrix --namespace productionOutput Format
{
"cluster": "production",
"audit_date": "2025-01-15",
"cluster_admin_subjects": 5,
"wildcard_roles": 3,
"escalation_risks": 2,
"unused_roles": 8,
"findings": [
{
"role": "custom-admin",
"issue": "Wildcard verbs and resources",
"severity": "critical",
"remediation": "Replace * with explicit verb and resource lists"
}
]
}standards.md0.7 KB
Standards - RBAC Hardening for Kubernetes
CIS Kubernetes Benchmark v1.9
- 5.1.1: Ensure cluster-admin role is only used where required
- 5.1.2: Minimize access to secrets
- 5.1.3: Minimize wildcard use in Roles and ClusterRoles
- 5.1.4: Minimize access to create pods
- 5.1.5: Ensure default service accounts are not actively used
- 5.1.6: Ensure Service Account Tokens are only mounted where necessary
NIST SP 800-190
- Section 3.4: Orchestrator security -- access control hardening
- Section 4.4: Countermeasures for orchestrator vulnerabilities
MITRE ATT&CK
- T1078.004: Valid Accounts: Cloud Accounts -- compromised service accounts
- T1098: Account Manipulation -- RBAC escalation
- T1069: Permission Groups Discovery -- enumerating RBAC bindings
workflows.md0.7 KB
Workflows - RBAC Hardening
Hardening Workflow
- Audit all existing ClusterRoleBindings and RoleBindings
- Identify overprivileged accounts (cluster-admin sprawl)
- Create namespace-scoped Roles with minimum required permissions
- Migrate workloads to dedicated service accounts
- Disable automountServiceAccountToken on default service accounts
- Integrate OIDC for user authentication
- Deploy RBAC monitoring and alerting
- Schedule quarterly RBAC reviews
Continuous Compliance
- Weekly: automated RBAC audit with rbac-lookup
- Monthly: review new RoleBindings created in past 30 days
- Quarterly: full access review with stakeholder sign-off
- Annually: penetration test RBAC boundaries
Scripts 2
agent.py9.7 KB
#!/usr/bin/env python3
"""Kubernetes RBAC hardening audit agent.
Audits Kubernetes Role-Based Access Control configuration for security
weaknesses including overly permissive ClusterRoles, wildcard permissions,
privilege escalation paths, and service account misconfigurations.
"""
import argparse
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
def run_kubectl(args_list, timeout=60):
"""Execute kubectl and return parsed JSON."""
cmd = ["kubectl"] + args_list + ["-o", "json"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
if result.returncode != 0:
return None
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
return None
def audit_cluster_roles():
"""Audit ClusterRoles for dangerous permissions."""
findings = []
data = run_kubectl(["get", "clusterroles"])
if not data:
return findings
dangerous_verbs = {"*", "create", "update", "patch", "delete"}
dangerous_resources = {"secrets", "pods/exec", "clusterroles", "clusterrolebindings",
"roles", "rolebindings", "serviceaccounts", "nodes"}
escalation_resources = {"clusterroles", "clusterrolebindings", "roles", "rolebindings"}
for role in data.get("items", []):
name = role.get("metadata", {}).get("name", "")
if name.startswith("system:"):
continue # Skip system roles
for rule in role.get("rules", []):
verbs = set(rule.get("verbs", []))
resources = set(rule.get("resources", []))
api_groups = rule.get("apiGroups", [])
# Wildcard everything
if "*" in verbs and "*" in resources:
findings.append({
"type": "wildcard_all", "role": name, "kind": "ClusterRole",
"severity": "CRITICAL",
"detail": "Full wildcard access (verbs: *, resources: *)",
"recommendation": "Replace with specific verbs and resources",
})
# Secrets access
if resources & {"secrets", "*"} and verbs & {"get", "list", "watch", "*"}:
findings.append({
"type": "secrets_access", "role": name, "kind": "ClusterRole",
"severity": "HIGH",
"detail": f"Can read secrets (verbs: {verbs & {'get', 'list', 'watch', '*'}})",
})
# Pod exec
if "pods/exec" in resources or ("pods" in resources and "create" in verbs):
findings.append({
"type": "pod_exec", "role": name, "kind": "ClusterRole",
"severity": "HIGH",
"detail": "Can exec into pods (container escape risk)",
})
# Privilege escalation via RBAC modification
if resources & escalation_resources and verbs & {"create", "update", "patch", "*"}:
findings.append({
"type": "rbac_escalation", "role": name, "kind": "ClusterRole",
"severity": "CRITICAL",
"detail": f"Can modify RBAC resources: {resources & escalation_resources}",
})
# Node access
if "nodes" in resources and verbs & {"get", "list", "proxy", "*"}:
findings.append({
"type": "node_access", "role": name, "kind": "ClusterRole",
"severity": "MEDIUM",
"detail": "Can access node resources",
})
return findings
def audit_cluster_role_bindings():
"""Audit ClusterRoleBindings for overly broad subject assignments."""
findings = []
data = run_kubectl(["get", "clusterrolebindings"])
if not data:
return findings
for binding in data.get("items", []):
name = binding.get("metadata", {}).get("name", "")
if name.startswith("system:"):
continue
role_ref = binding.get("roleRef", {})
role_name = role_ref.get("name", "")
subjects = binding.get("subjects", [])
for subject in subjects:
kind = subject.get("kind", "")
subj_name = subject.get("name", "")
namespace = subject.get("namespace", "")
# Cluster-admin binding
if role_name == "cluster-admin":
findings.append({
"type": "cluster_admin_binding",
"binding": name, "subject": f"{kind}/{subj_name}",
"severity": "CRITICAL",
"detail": f"cluster-admin bound to {kind} '{subj_name}'",
})
# Group bindings to all authenticated/unauthenticated
if kind == "Group" and subj_name in ("system:authenticated", "system:unauthenticated"):
findings.append({
"type": "broad_group_binding",
"binding": name, "subject": subj_name,
"severity": "CRITICAL" if subj_name == "system:unauthenticated" else "HIGH",
"detail": f"Role '{role_name}' bound to group '{subj_name}'",
})
# Default service account bindings
if kind == "ServiceAccount" and subj_name == "default":
findings.append({
"type": "default_sa_binding",
"binding": name, "subject": f"default SA in {namespace}",
"severity": "MEDIUM",
"detail": f"Role '{role_name}' bound to default service account",
})
return findings
def audit_service_accounts(namespace=None):
"""Audit service accounts for misconfigurations."""
findings = []
cmd = ["get", "serviceaccounts", "--all-namespaces"] if not namespace else ["get", "serviceaccounts", "-n", namespace]
data = run_kubectl(cmd)
if not data:
return findings
for sa in data.get("items", []):
name = sa.get("metadata", {}).get("name", "")
ns = sa.get("metadata", {}).get("namespace", "")
automount = sa.get("automountServiceAccountToken", None)
if name == "default" and automount is not False:
findings.append({
"type": "default_sa_automount",
"namespace": ns, "service_account": name,
"severity": "MEDIUM",
"detail": f"Default SA in '{ns}' has automountServiceAccountToken enabled",
"recommendation": "Set automountServiceAccountToken: false on default SA",
})
secrets = sa.get("secrets", [])
if len(secrets) > 1:
findings.append({
"type": "sa_multiple_secrets",
"namespace": ns, "service_account": name,
"severity": "LOW",
"detail": f"SA has {len(secrets)} token secrets",
})
return findings
def format_summary(role_findings, binding_findings, sa_findings):
"""Print RBAC audit summary."""
all_findings = role_findings + binding_findings + sa_findings
print(f"\n{'='*60}")
print(f" Kubernetes RBAC Hardening Audit")
print(f"{'='*60}")
print(f" ClusterRole Issues : {len(role_findings)}")
print(f" Binding Issues : {len(binding_findings)}")
print(f" ServiceAccount Issues : {len(sa_findings)}")
print(f" Total Findings : {len(all_findings)}")
severity_counts = {}
for f in all_findings:
sev = f.get("severity", "INFO")
severity_counts[sev] = severity_counts.get(sev, 0) + 1
print(f"\n By Severity:")
for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]:
count = severity_counts.get(sev, 0)
if count:
print(f" {sev:10s}: {count}")
if all_findings:
print(f"\n Top Findings:")
for f in sorted(all_findings, key=lambda x: {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2}.get(x["severity"], 9))[:15]:
print(f" [{f['severity']:8s}] {f['type']:25s} | {f.get('detail', '')[:50]}")
return severity_counts
def main():
parser = argparse.ArgumentParser(description="Kubernetes RBAC hardening audit agent")
parser.add_argument("--namespace", "-n", help="Specific namespace to audit")
parser.add_argument("--kubeconfig", help="Path to kubeconfig")
parser.add_argument("--skip-roles", action="store_true")
parser.add_argument("--skip-bindings", action="store_true")
parser.add_argument("--skip-sa", action="store_true")
parser.add_argument("--output", "-o", help="Output JSON report")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
if args.kubeconfig:
os.environ["KUBECONFIG"] = args.kubeconfig
role_findings = [] if args.skip_roles else audit_cluster_roles()
binding_findings = [] if args.skip_bindings else audit_cluster_role_bindings()
sa_findings = [] if args.skip_sa else audit_service_accounts(args.namespace)
severity_counts = format_summary(role_findings, binding_findings, sa_findings)
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "K8s RBAC Audit",
"role_findings": role_findings,
"binding_findings": binding_findings,
"sa_findings": sa_findings,
"severity_counts": severity_counts,
"risk_level": (
"CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
else "HIGH" if severity_counts.get("HIGH", 0) > 0
else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0
else "LOW"
),
}
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
elif args.verbose:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
process.py6.9 KB
#!/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()