container security

Implementing RBAC Hardening for Kubernetes

Harden Kubernetes Role-Based Access Control by implementing least-privilege policies, auditing role bindings, eliminating cluster-admin sprawl, and integrating external identity providers.

access-controliamkubernetesleast-privilegeoidcrbacsecurity-hardeningservice-accounts
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

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.io

3. 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.crt

RBAC 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 wide

rakkess (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-processor

References

Source materials

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 pyyaml

Authentication

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 findings

Find 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_subjects

Detect 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 findings

Audit 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 findings

Find 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 unused

kubectl 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 production

Output 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

  1. Audit all existing ClusterRoleBindings and RoleBindings
  2. Identify overprivileged accounts (cluster-admin sprawl)
  3. Create namespace-scoped Roles with minimum required permissions
  4. Migrate workloads to dedicated service accounts
  5. Disable automountServiceAccountToken on default service accounts
  6. Integrate OIDC for user authentication
  7. Deploy RBAC monitoring and alerting
  8. 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
Display-only source. This catalog never executes bundled scripts.
#!/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
Display-only source. This catalog never executes bundled scripts.
#!/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()

Assets 1

template.mdtext/markdown · 0.7 KB
Keep exploring