> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Minimize Access To The Approval Sub-Resource Of CertificateSigningRequests

### More Info:

Access to approve CertificateSigningRequests can be abused to issue high-privilege certificates and create cluster-admin level users. This access should be limited to trusted administrators only.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS AKS

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. On any machine with kubectl access, list all Roles/ClusterRoles that can approve CSRs:
           ```bash theme={null}
           kubectl get clusterroles -o json \
             | jq -r '.items[]
               | select(.rules[]?
                 | select(.apiGroups[]? == "certificates.k8s.io")
                 | select(.resources[]? == "certificatesigningrequests/approval")
                 | select(.verbs[]? | IN("update","*"))
               )
               | .metadata.name' | sort -u

           kubectl get roles --all-namespaces -o json \
             | jq -r '.items[]
               | select(.rules[]?
                 | select(.apiGroups[]? == "certificates.k8s.io")
                 | select(.resources[]? == "certificatesigningrequests/approval")
                 | select(.verbs[]? | IN("update","*"))
               )
               | "\(.metadata.namespace)/\(.metadata.name)"' | sort -u
           ```

        2. For each identified Role/ClusterRole, review its intent and current subjects (who gets it):
           ```bash theme={null}
           # Example for a ClusterRole
           kubectl get clusterrole <NAME> -o yaml

           # See who is bound to it
           kubectl get clusterrolebindings -o yaml \
             | yq 'select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<NAME>")'

           # Example for a namespaced Role
           kubectl get role -n <NAMESPACE> <NAME> -o yaml
           kubectl get rolebindings -n <NAMESPACE> -o yaml \
             | yq 'select(.roleRef.kind == "Role" and .roleRef.name == "<NAME>")'
           ```
           Decide whether each subject (user/group/serviceaccount) is a trusted admin who truly needs CSR approval.

        3. For access that is not justified, remove or narrow the binding instead of deleting the Role/ClusterRole outright:
           ```bash theme={null}
           # Edit a specific ClusterRoleBinding to remove non-admin subjects
           kubectl edit clusterrolebinding <BINDING_NAME>

           # Or a RoleBinding in a namespace
           kubectl edit rolebinding -n <NAMESPACE> <BINDING_NAME>
           ```
           In the editor, delete subjects that should not have CSR approval. Save to apply.

        4. If a Role/ClusterRole is used exclusively to provide CSR approval to non-admins and is no longer needed, remove or tighten its rule:
           ```bash theme={null}
           # Edit to remove or restrict the CSR approval rule
           kubectl edit clusterrole <NAME>
           # or
           kubectl edit role -n <NAMESPACE> <NAME>
           ```
           In the `rules`, either delete the `certificatesigningrequests/approval` entry or reduce `verbs` so `update` (and `*` if present) is not granted unless absolutely required.

        5. For cases where limited, justified CSR approvers are required (e.g., a small security team), ensure they are mapped to a dedicated admin group or service account instead of broad subjects like `system:authenticated` or large SSO groups. Adjust bindings accordingly:
           ```bash theme={null}
           kubectl edit clusterrolebinding <BINDING_NAME>
           # Replace broad/unknown groups with specific, documented admin identities.
           ```

        6. Verify that only the intended admin identities retain CSR approval rights:
           ```bash theme={null}
           # Re-run discovery
           kubectl get clusterroles -o json | jq -r '...same jq as step 1...'
           kubectl get roles --all-namespaces -o json | jq -r '...same jq as step 1...'
           ```
           For each remaining Role/ClusterRole listed, confirm its bindings reference only trusted admin users/groups/serviceaccounts as per your access policy.
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 1. List all ClusterRoles that can approve CSRs
        # Run on: any machine with kubectl access
        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | (.verbs[]? | IN("update","*"))
                and (.resources[]? | IN("certificatesigningrequests/approval","certificatesigningrequests","*"))
              )
            | .metadata.name
          ' | sort -u
        ```

        Problem indication: Any ClusterRole in this list is able (directly or via `*`) to approve CSRs. Each listed role must be reviewed to decide if that power is justified and restricted to trusted admins.

        ```bash theme={null}
        # 2. Show full definitions of those ClusterRoles for detailed review
        # Replace <CLUSTERROLE_NAME> with each name from the previous command
        kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
        ```

        What to look for (problem indicators):

        * `resources` includes `certificatesigningrequests/approval` or `certificatesigningrequests` or `*`
        * `verbs` includes `update` or `*`
        * The role is bound to broad subjects (e.g., `system:authenticated`, wide groups, or service accounts used by general workloads), not just a very small set of trusted admins.

        ```bash theme={null}
        # 3. Identify what subjects are bound to each CSR-approving ClusterRole
        # Run once per ClusterRole name from step 1
        kubectl get clusterrolebindings -o json \
          | jq -r '
            .items[]
            | select(.roleRef.kind=="ClusterRole" and .roleRef.name=="<CLUSTERROLE_NAME>")
            | .metadata.name as $rb
            | .subjects[]
            | "\($rb) \t \(.kind) \t \(.namespace // "-") \t \(.name)"
          ' | sort
        ```

        Problem indication: Any binding where:

        * `kind` is `Group` with a very broad group (e.g., `system:authenticated`, `system:serviceaccounts`, or an org-wide group not limited to cluster admins).
        * `kind` is `ServiceAccount` used by general workloads, CI/CD, or applications, rather than a tightly-controlled admin SA.
        * `kind` is `User` representing many operators instead of a very small set of trusted admins.

        ```bash theme={null}
        # 4. Check Namespaced Roles (if using aggregated or custom approval flows)
        kubectl get roles --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | (.verbs[]? | IN("update","*"))
                and (.resources[]? | IN("certificatesigningrequests/approval","certificatesigningrequests","*"))
              )
            | "\(.metadata.namespace)\t\(.metadata.name)"
          ' | sort
        ```

        Then inspect and trace bindings:

        ```bash theme={null}
        # 4a. Inspect a specific Role
        kubectl get role -n <NAMESPACE> <ROLE_NAME> -o yaml

        # 4b. See who has that Role in that namespace
        kubectl get rolebindings -n <NAMESPACE> -o json \
          | jq -r '
            .items[]
            | select(.roleRef.kind=="Role" and .roleRef.name=="<ROLE_NAME>")
            | .metadata.name as $rb
            | .subjects[]
            | "\($rb) \t \(.kind) \t \(.namespace // "-") \t \(.name)"
          ' | sort
        ```

        Problem indication: Same patterns as for ClusterRoles—any Role with CSR approval power that is bound to non-admin or broadly scoped subjects.

        ```bash theme={null}
        # 5. (Optional) Quick human-readable summary of all CSR-approval rules
        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | .metadata.name as $name
            | [
                .rules[]
                | select(
                    (.verbs[]? | IN("update","*"))
                    and (.resources[]? | IN("certificatesigningrequests/approval","certificatesigningrequests","*"))
                  )
                | "  - apiGroups: \(.apiGroups|join(",")) resources: \(.resources|join(",")) verbs: \(.verbs|join(","))"
              ] as $matches
            | select($matches|length>0)
            | $name, ($matches[])
          '
        ```

        Use this summary to visually confirm which roles can approve CSRs and decide, case by case, whether that is acceptable or needs to be restricted.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Report Roles/ClusterRoles that can approve CertificateSigningRequests
        # Run on: any machine with kubectl access and current kubeconfig context

        set -euo pipefail

        echo "=== ClusterRoles with CSR approval rights ==="
        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | select(
                (.rules // [])
                | map(
                    (.resources // []) as $r
                    | (.verbs // []) as $v
                    | select(
                        (
                          # Direct CSR approval subresource
                          ($r | index("certificatesigningrequests/approval"))
                          # Wildcards that include it
                          or ($r | index("certificatesigningrequests") and ($v | index("*") or $v | index("update") or $v | index("patch")))
                          or (($r | index("*") or $r | index("*/approval")) and ($v | index("*") or $v | index("update") or $v | index("patch")))
                        )
                    )
                )
                | length > 0
              )
            | .metadata.name
          ' \
          | sort -u

        echo
        echo "=== Roles (namespaced) with CSR approval rights ==="
        kubectl get roles -A -o json \
          | jq -r '
            .items[]
            | select(
                (.rules // [])
                | map(
                    (.resources // []) as $r
                    | (.verbs // []) as $v
                    | select(
                        (
                          ($r | index("certificatesigningrequests/approval"))
                          or ($r | index("certificatesigningrequests") and ($v | index("*") or $v | index("update") or $v | index("patch")))
                          or (($r | index("*") or $r | index("*/approval")) and ($v | index("*") or $v | index("update") or $v | index("patch")))
                        )
                    )
                )
                | length > 0
              )
            | [.metadata.namespace, .metadata.name]
            | @tsv
          ' \
          | sort -u

        echo
        echo "=== ClusterRoleBindings & RoleBindings that grant CSR approval rights ==="

        # Helper: list RBAC objects that bind to roles/clusterroles from stdin
        list_bindings_for_roles() {
          local role_kind="$1" # ClusterRole or Role
          while read -r name ns; do
            if [[ -z "${name:-}" ]]; then
              continue
            fi
            if [[ "${role_kind}" == "ClusterRole" ]]; then
              kubectl get clusterrolebindings -o json \
                | jq -r --arg rn "$name" '
                    .items[]
                    | select(.roleRef.kind == "ClusterRole" and .roleRef.name == $rn)
                    | .metadata.name
                  ' \
                | sed "s/^/${role_kind} ${name} <- ClusterRoleBinding: /"
              kubectl get rolebindings -A -o json \
                | jq -r --arg rn "$name" '
                    .items[]
                    | select(.roleRef.kind == "ClusterRole" and .roleRef.name == $rn)
                    | [.metadata.namespace, .metadata.name]
                    | @tsv
                  ' \
                | sed "s/^/${role_kind} ${name} <- RoleBinding: /"
            else
              # Namespaced Role
              kubectl get rolebindings -n "$ns" -o json \
                | jq -r --arg rn "$name" '
                    .items[]
                    | select(.roleRef.kind == "Role" and .roleRef.name == $rn)
                    | .metadata.name
                  ' \
                | sed "s/^/${role_kind} ${ns}/${name} <- RoleBinding: /"
            fi
          done
        }

        echo "# Bindings for ClusterRoles with CSR approval:"
        clusterroles_with_csr=$(kubectl get clusterroles -o json \
          | jq -r '
              .items[]
              | select(
                  (.rules // [])
                  | map(
                      (.resources // []) as $r
                      | (.verbs // []) as $v
                      | select(
                          (
                            ($r | index("certificatesigningrequests/approval"))
                            or ($r | index("certificatesigningrequests") and ($v | index("*") or $v | index("update") or $v | index("patch")))
                            or (($r | index("*") or $r | index("*/approval")) and ($v | index("*") or $v | index("update") or $v | index("patch")))
                          )
                      )
                  )
                  | length > 0
                )
              | .metadata.name
            ' | sort -u)

        if [[ -n "${clusterroles_with_csr}" ]]; then
          awk '{print $1}' <<< "${clusterroles_with_csr}" | list_bindings_for_roles "ClusterRole"
        else
          echo "None"
        fi

        echo
        echo "# Bindings for Roles with CSR approval:"
        roles_with_csr=$(kubectl get roles -A -o json \
          | jq -r '
              .items[]
              | select(
                  (.rules // [])
                  | map(
                      (.resources // []) as $r
                      | (.verbs // []) as $v
                      | select(
                          (
                            ($r | index("certificatesigningrequests/approval"))
                            or ($r | index("certificatesigningrequests") and ($v | index("*") or $v | index("update") or $v | index("patch")))
                            or (($r | index("*") or $r | index("*/approval")) and ($v | index("*") or $v | index("update") or $v | index("patch")))
                          )
                      )
                  )
                  | length > 0
                )
              | [.metadata.namespace, .metadata.name]
              | @tsv
            ' | sort -u)

        if [[ -n "${roles_with_csr}" ]]; then
          list_bindings_for_roles "Role" <<< "${roles_with_csr}"
        else
          echo "None"
        fi

        echo
        echo "=== Interpretation guide ==="
        echo "- Any ClusterRole or Role listed above has the ability (directly or via wildcards) to approve CSRs."
        echo "- Investigate bindings that grant these roles to service accounts, groups, or users that are not trusted administrators."
        echo "- Overly broad examples to flag:"
        echo "  * Roles/ClusterRoles with resources: ['*'] and verbs including '*' or 'update' or 'patch'."
        echo "  * Any binding of such roles to default service accounts or wide groups (e.g., system:authenticated)."
        ```

        Problematic output indicators to review and potentially restrict:

        * ClusterRoles with names like `edit`, `admin`, or custom application roles that appear in the “ClusterRoles with CSR approval rights” list.
        * Roles in application namespaces (non-system namespaces) that appear in the “Roles (namespaced) with CSR approval rights” list.
        * Any of the listed Roles/ClusterRoles bound (via ClusterRoleBinding or RoleBinding) to:
          * `system:authenticated`, `system:serviceaccounts`, or similar broad groups.
          * Default service accounts (e.g., `default` in any namespace).
          * Application-specific service accounts that are not trusted cluster administrators.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
