Back to Checks
kubernetes

User or group lacks update and patch access to the certificatesigningrequests/approval sub-resource

rbac_minimize_csr_approval_access
Service: rbacSeverity: high
OfficialBuilt-inVerified
Categories:identity-accesscluster-security
Resource Type:ClusterRoleBinding

RBAC assignments that grant update or patch on the certificatesigningrequests/approval subresource to users or groups via cluster-wide roles and bindings.

This highlights principals allowed to approve CSRs based on permissions defined in referenced ClusterRoles.

Prowler logoProwlerprowler-provider-kubernetes

Risk

Excess CSR approval rights enable privilege escalation. A malicious user can approve CSRs to mint client certificates for arbitrary identities, gaining unauthorized API access, impersonating system components, and enabling data exfiltration and tampering, harming confidentiality and integrity.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler kubernetes --checks rbac_minimize_csr_approval_access

Recommendation

Apply least privilege: allow CSR approval only to a small, trusted approver role.

  • Enforce separation of duties between CSR creation and approval
  • Prefer automated approver controllers over manual grants
  • Regularly review RBAC and remove broad ClusterRoleBindings
  • Use defense in depth with auditing and short-lived credentials

Remediation

CLI

kubectl delete clusterrolebinding <example_resource_name>

Terraform

resource "kubernetes_cluster_role" "safe" {
  metadata { name = "<example_resource_name>" }
  # No rules included -> ensures no update/patch on certificatesigningrequests/approval
}

resource "kubernetes_cluster_role_binding" "safe_bind" {
  metadata { name = "<example_resource_name>" }
  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = kubernetes_cluster_role.safe.metadata[0].name  # Critical: binds subject to a role without CSR approval rights
  }
  subject {
    kind      = "User"  # Use "Group" if binding a group
    name      = "<example_user_or_group>"
    api_group = "rbac.authorization.k8s.io"
  }
}

Other

1Open the Kubernetes Dashboard
2Go to Access control (RBAC) > Cluster Role Bindings
3Find the binding that assigns a User or Group to a role permitting certificatesigningrequests/approval
4Click the binding, then Delete to remove it
5If you must keep the binding: go to Access control (RBAC) > Cluster Roles, open the referenced role, click Edit (YAML), remove the rule that has resource "certificatesigningrequests/approval" with verbs ["update", "patch"], and save

Check Code

from prowler.lib.check.models import Check, Check_Report_Kubernetes
from prowler.providers.kubernetes.services.rbac.lib.role_permissions import (
    is_rule_allowing_permissions,
)
from prowler.providers.kubernetes.services.rbac.rbac_client import rbac_client

verbs = ["update", "patch"]
resources = ["certificatesigningrequests/approval"]
api_groups = ["certificates.k8s.io"]


class rbac_minimize_csr_approval_access(Check):
    def execute(self) -> Check_Report_Kubernetes:
        findings = []
        # Collect unique subjects and the ClusterRole names bound to them
        subjects_bound_roles = {}
        for crb in rbac_client.cluster_role_bindings.values():
            for subject in crb.subjects:
                # CIS benchmarks scope these checks to human identities only
                if subject.kind in ["User", "Group"]:
                    key = (subject.kind, subject.name, subject.namespace)
                    if key not in subjects_bound_roles:
                        subjects_bound_roles[key] = (subject, set())
                    subjects_bound_roles[key][1].add(crb.roleRef.name)

        cluster_roles_by_name = {
            cr.metadata.name: cr for cr in rbac_client.cluster_roles.values()
        }
        for _, (subject, role_names) in subjects_bound_roles.items():
            report = Check_Report_Kubernetes(metadata=self.metadata(), resource=subject)
            report.resource_name = f"{subject.kind}:{subject.name}"
            report.resource_id = f"{subject.kind}/{subject.name}"
            report.status = "PASS"
            report.status_extended = f"User or group '{subject.name}' does not have access to update the CSR approval sub-resource."
            for role_name in role_names:
                cr = cluster_roles_by_name.get(role_name)
                if cr and is_rule_allowing_permissions(
                    cr.rules, resources, verbs, api_groups
                ):
                    report.status = "FAIL"
                    report.status_extended = f"User or group '{subject.name}' has access to update the CSR approval sub-resource."
                    break
            findings.append(report)

        return findings