Skip to main content

Minimize Access To The Approval Sub-Resource Of

More Info:

Approving CertificateSigningRequests can issue client certificates that impersonate any identity. Limit access to the approval sub-resource.

Risk Level

Medium

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. List all ClusterRoles with CSR approval access

    • Run on: any machine with kubectl access
    kubectl get clusterroles -o json \
    | jq -r '
    .items[]
    | select(
    .rules[]
    | select(
    (.apiGroups[]? == "certificates.k8s.io")
    and (.resources[]? == "certificatesigningrequests/approval")
    )
    )
    | .metadata.name
    ' | sort -u

    Save the resulting ClusterRole names; these are the ones to review.

  2. Inspect each identified ClusterRole’s rules and intended use

    • For each ClusterRole name from step 1, run:
    kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
    • Review:
      • Who/what this role is meant for (name/annotations/labels, any documented use).
      • Exact rules granting access to certificatesigningrequests/approval (verbs like approve, update, patch via that sub-resource).
  3. Determine whether approval access is strictly required
    For each ClusterRole:

    • Identify all RoleBindings/ClusterRoleBindings using it:
      kubectl get clusterrolebindings -o yaml \
      | yq 'select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<CLUSTERROLE_NAME>")'
      kubectl get rolebindings -A -o yaml \
      | yq 'select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<CLUSTERROLE_NAME>")'
    • For each bound subject (user, group, service account), confirm with application/operations owners whether they actually need to approve CSRs, or only to request/view them.
  4. Reduce or remove CSR approval rights where not required
    For each ClusterRole where approval is not strictly needed:

    • Edit and remove the certificatesigningrequests/approval resource from its rules (or remove the rule entirely if only for approval):
      kubectl edit clusterrole <CLUSTERROLE_NAME>
    • In the editor, under rules:, delete any entry that includes:
      apiGroups:
      - certificates.k8s.io
      resources:
      - certificatesigningrequests/approval
    • If no other permissions in the ClusterRole are required, consider removing the bindings or deleting the ClusterRole:
      kubectl delete clusterrolebinding <BINDING_NAME>
      kubectl delete clusterrole <CLUSTERROLE_NAME>
  5. Restrict necessary approval access to the minimum set of subjects
    Where approval access is truly needed:

    • Ensure the ClusterRole scope is minimal (only CSR approval and closely related, necessary verbs).
    • Tighten bindings to the smallest possible set of users/groups/service accounts, and use names/labels that clearly indicate high-privilege usage.
    • Update bindings as needed:
      kubectl edit clusterrolebinding <BINDING_NAME>
      kubectl edit rolebinding -n <NAMESPACE> <BINDING_NAME>
  6. Re-verify that unnecessary approval access is removed

    • Re-run the discovery to confirm only intentional roles remain:
      kubectl get clusterroles -o json \
      | jq -r '
      .items[]
      | select(
      .rules[]
      | select(
      (.apiGroups[]? == "certificates.k8s.io")
      and (.resources[]? == "certificatesigningrequests/approval")
      )
      )
      | .metadata.name
      ' | sort -u
    • Cross-check each remaining ClusterRole and its bindings as in steps 2–3 to ensure that all holders of CSR approval rights are explicitly justified.
Using kubectl
# 1. List ClusterRoles that can approve CertificateSigningRequests
# Run on: any machine with kubectl access
kubectl get clusterroles -o json | jq -r '
.items[]
| select(
.rules[]
| select(
(.apiGroups[]? == "certificates.k8s.io")
and (.resources[]? == "certificatesigningrequests/approval")
)
)
| .metadata.name
' | sort -u

Problem indication:
Any ClusterRole name in this list has permission to approve CSRs. Each must be reviewed to confirm that this access is truly required.


# 2. Show full definitions of those ClusterRoles for detailed review
# Replace <CLUSTERROLE_NAME> with one name from the list above
kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml

What to look for (problem indications):

In the rules: section:

  • apiGroups contains certificates.k8s.io
  • resources contains certificatesigningrequests/approval
  • verbs includes powerful actions such as update or *

Example of a risky rule:

- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/approval
verbs:
- update
- '*'

Flag as a concern when:

  • The ClusterRole is generic/broad (e.g. used by many users or groups).
  • The ClusterRole is bound to wide subjects such as system:authenticated, system:masters, or broad groups.

# 3. List all ClusterRoleBindings and RoleBindings that reference these ClusterRoles
# Run once per ClusterRole with approval rights
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<CLUSTERROLE_NAME>")
| .metadata.name
'

kubectl get rolebindings -A -o json | jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<CLUSTERROLE_NAME>")
| [.metadata.namespace, .metadata.name] | @tsv
'

Problem indication:
Bindings that attach these ClusterRoles to:

  • Very broad groups (e.g. system:authenticated, system:unauthenticated, or large SSO groups).
  • ServiceAccounts or users that do not have an explicit operational need to approve CSRs.

These bindings represent where the high‑risk approval capability is actually granted and should be manually reviewed and potentially tightened or removed.

Automation
#!/usr/bin/env bash
# Report ClusterRoles and RoleBindings that can approve CertificateSigningRequests

set -euo pipefail

echo "=== ClusterRoles with csr approvals (verbs: [* approve]) ==="
kubectl get clusterroles -o json | jq -r '
.items[]
| . as $cr
| ($cr.rules // [])
| map(
select(
((.resources // []) | index("certificatesigningrequests/approval"))
and
(
(.verbs // []) | index("*") or
(.verbs // []) | index("approve")
)
)
)
| select(length > 0)
| $cr.metadata.name
' | sort -u

echo
echo "=== Namespaced Roles with csr approvals (verbs: [* approve]) ==="
kubectl get roles --all-namespaces -o json | jq -r '
.items[]
| . as $r
| ($r.rules // [])
| map(
select(
((.resources // []) | index("certificatesigningrequests/approval"))
and
(
(.verbs // []) | index("*") or
(.verbs // []) | index("approve")
)
)
)
| select(length > 0)
| "\($r.metadata.namespace)\t\($r.metadata.name)"
' | sort -u

echo
echo "=== ClusterRoleBindings referencing those ClusterRoles ==="
problem_clusterroles=$(kubectl get clusterroles -o json | jq -r '
.items[]
| . as $cr
| ($cr.rules // [])
| map(
select(
((.resources // []) | index("certificatesigningrequests/approval"))
and
(
(.verbs // []) | index("*") or
(.verbs // []) | index("approve")
)
)
)
| select(length > 0)
| $cr.metadata.name
')

if [ -n "${problem_clusterroles}" ]; then
# shellcheck disable=SC2086
kubectl get clusterrolebindings -o json | jq -r --argjson names "$(printf '%s\n' $problem_clusterroles | jq -R . | jq -s .)" '
.items[]
| . as $crb
| if any($crb.roleRef.name; . as $n | ($names[] == $n)) then
{
name: $crb.metadata.name,
roleRef: $crb.roleRef,
subjects: ($crb.subjects // [])
}
else empty end
'
else
echo "No ClusterRoles with csr approval found; no ClusterRoleBindings to report."
fi

echo
echo "=== RoleBindings referencing those Roles (namespaced) ==="
problem_roles=$(kubectl get roles --all-namespaces -o json | jq -r '
.items[]
| . as $r
| ($r.rules // [])
| map(
select(
((.resources // []) | index("certificatesigningrequests/approval"))
and
(
(.verbs // []) | index("*") or
(.verbs // []) | index("approve")
)
)
)
| select(length > 0)
| "\($r.metadata.namespace)/\($r.metadata.name)"
')

if [ -n "${problem_roles}" ]; then
# shellcheck disable=SC2086
kubectl get rolebindings --all-namespaces -o json | jq -r --argjson names "$(printf '%s\n' $problem_roles | jq -R . | jq -s .)" '
.items[]
| . as $rb
| "\($rb.roleRef.namespace // $rb.metadata.namespace)/\($rb.roleRef.name)" as $ref
| if ($names[]? == $ref) then
{
namespace: $rb.metadata.namespace,
name: $rb.metadata.name,
roleRef: $rb.roleRef,
subjects: ($rb.subjects // [])
}
else empty end
'
else
echo "No Roles with csr approval found; no RoleBindings to report."
fi

echo
echo "=== Interpretation ==="
cat <<EOF
Any ClusterRole or Role listed above grants access to the
"certificatesigningrequests/approval" sub-resource with either:
- verb "approve", or
- verb "*", which implicitly includes "approve".

These are candidates for review. For each binding, verify that all
listed subjects (users, groups, service accounts) are explicitly
authorized to approve CSRs. If not, remove or restrict that permission.
EOF

Run this on any machine with kubectl and jq configured for the cluster.

Output indicating a problem:

  • Any ClusterRole or Role name shown in the first two sections.
  • Any ClusterRoleBinding or RoleBinding listed, especially where subjects are broad (e.g. system:authenticated, system:masters, or wide service-account patterns).