> ## 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 Webhook Configuration Objects

### More Info:

Access to validating or mutating webhook configurations can be abused to escalate privileges or disrupt cluster operations. It 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. **List all Roles/ClusterRoles with webhook configuration permissions**\
           Run on: any machine with kubectl access
           ```bash theme={null}
           kubectl get clusterroles -o json | jq -r '
             .items[]
             | select(
                 (.rules[]? | .resources[]? | contains("validatingwebhookconfigurations"))
                 or
                 (.rules[]? | .resources[]? | contains("mutatingwebhookconfigurations"))
               )
             | .metadata.name' | sort -u
           ```
           ```bash theme={null}
           kubectl get roles -A -o json | jq -r '
             .items[]
             | select(
                 (.rules[]? | .resources[]? | contains("validatingwebhookconfigurations"))
                 or
                 (.rules[]? | .resources[]? | contains("mutatingwebhookconfigurations"))
               )
             | "\(.metadata.namespace)/\(.metadata.name)"' | sort -u
           ```

        2. **Inspect the exact permissions on each identified Role/ClusterRole**\
           Run on: any machine with kubectl access\
           For each ClusterRole name from step 1:
           ```bash theme={null}
           kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
           ```
           For each namespaced Role from step 1:
           ```bash theme={null}
           kubectl get role -n <NAMESPACE> <ROLE_NAME> -o yaml
           ```
           Review `rules` for verbs like `create`, `update`, `patch`, or `delete` on `validatingwebhookconfigurations` or `mutatingwebhookconfigurations`. Flag any Role/ClusterRole not strictly needed for trusted cluster administration.

        3. **Identify who is bound to these Roles/ClusterRoles**\
           Run on: any machine with kubectl access\
           For each ClusterRole:
           ```bash theme={null}
           kubectl get clusterrolebindings -o json | jq -r '
             .items[]
             | select(.roleRef.kind=="ClusterRole" and .roleRef.name=="<CLUSTERROLE_NAME>")
             | .metadata.name'
           ```
           For each Role:
           ```bash theme={null}
           kubectl get rolebindings -n <NAMESPACE> -o json | jq -r '
             .items[]
             | select(.roleRef.kind=="Role" and .roleRef.name=="<ROLE_NAME>")
             | .metadata.name'
           ```
           Then inspect each binding to see which users/groups/serviceaccounts have access:
           ```bash theme={null}
           kubectl get clusterrolebinding <CRB_NAME> -o yaml
           kubectl get rolebinding -n <NAMESPACE> <RB_NAME> -o yaml
           ```

        4. **Decide required vs. unnecessary access**
           * Classify each subject (user, group, serviceaccount) as:
             * Trusted administrator requiring webhook configuration management, or
             * Application/operator component needing only read access (if any), or
             * Unnecessary recipient of webhook configuration write access.
           * For non-admin components that must manage their own webhooks, prefer narrowly scoped Roles limited to their specific webhooks rather than broad cluster-wide permissions.

        5. **Adjust RBAC to remove or narrow write access**\
           Run on: any machine with kubectl access\
           a) To remove an unnecessary binding entirely:
           ```bash theme={null}
           kubectl delete clusterrolebinding <CRB_NAME>
           kubectl delete rolebinding -n <NAMESPACE> <RB_NAME>
           ```
           b) To narrow a Role/ClusterRole (e.g., drop write verbs or webhook resources), edit it:
           ```bash theme={null}
           kubectl edit clusterrole <CLUSTERROLE_NAME>
           kubectl edit role -n <NAMESPACE> <ROLE_NAME>
           ```
           In the editor, remove `create`, `update`, `patch`, `delete` verbs or remove `validatingwebhookconfigurations` / `mutatingwebhookconfigurations` from `resources` for any non-admin use. Save to apply.

        6. **Verify resulting access is limited to trusted administrators**\
           Run on: any machine with kubectl access\
           Re-run the discovery and manually confirm bindings:
           ```bash theme={null}
           kubectl get clusterroles -o json | jq -r '
             .items[]
             | select(
                 (.rules[]? | .resources[]? | contains("validatingwebhookconfigurations"))
                 or
                 (.rules[]? | .resources[]? | contains("mutatingwebhookconfigurations"))
               )
             | .metadata.name' | sort -u
           kubectl get roles -A -o json | jq -r '
             .items[]
             | select(
                 (.rules[]? | .resources[]? | contains("validatingwebhookconfigurations"))
                 or
                 (.rules[]? | .resources[]? | contains("mutatingwebhookconfigurations"))
               )
             | "\(.metadata.namespace)/\(.metadata.name)"' | sort -u
           ```
           For the remaining Roles/ClusterRoles, confirm via their RoleBindings/ClusterRoleBindings that only trusted administrator identities retain write access to webhook configuration objects.
      </Accordion>

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

        **What to look for**

        * Output is a list of `ClusterRole` names.
        * Potential problems:
          * Generic or broad roles like `cluster-admin` (expected but high‑risk) and any custom roles with non-admin names (e.g. `dev-team`, `ci-runner`, `viewer-plus`) that appear here.
          * Any role clearly intended for application workloads, CI/CD, or non-ops users.

        ***

        ```bash theme={null}
        # 2) Inspect each suspicious ClusterRole in detail
        # Replace <CLUSTERROLE_NAME> with a name from the previous output
        kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
        ```

        **What to look for**

        * Problematic if:
          * `resources` includes `validatingwebhookconfigurations` and `verbs` includes powerful verbs (`create`, `update`, `patch`, `delete`, `deletecollection`, or `*`).
          * `apiGroups` is `admissionregistration.k8s.io` with broad wildcard usage:
            * `resources: ["*"]` or `verbs: ["*"]`.
        * Acceptable only for tightly controlled admin/cluster-operations roles.

        ***

        ```bash theme={null}
        # 3) List Roles (namespace-scoped) with access to validating webhooks (should normally be none)
        kubectl get role --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(
                (.rules[]?
                  | select(
                      (.resources[]? | IN("validatingwebhookconfigurations")) and
                      (.verbs[]? | IN("create","update","patch","delete","deletecollection","*"))
                    )
                )?
              )
            | .metadata.namespace + ":" + .metadata.name
          ' | sort -u
        ```

        **What to look for**

        * In most clusters, this should return **no results**.
        * Any Role here is suspicious, especially in application namespaces (e.g. `default`, `dev`, `staging`, `prod`).

        ***

        ```bash theme={null}
        # 4) Repeat for mutating webhook configurations: ClusterRoles
        kubectl get clusterrole -o json \
          | jq -r '
            .items[]
            | select(
                (.rules[]?
                  | select(
                      (.resources[]? | IN("mutatingwebhookconfigurations")) and
                      (.verbs[]? | IN("create","update","patch","delete","deletecollection","*"))
                    )
                )?
              )
            | .metadata.name
          ' | sort -u
        ```

        ```bash theme={null}
        # 5) Inspect each suspicious ClusterRole for mutating webhooks
        kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
        ```

        **What to look for**

        * Same criteria as validating webhooks: strong verbs on `mutatingwebhookconfigurations` or wildcard rules in `admissionregistration.k8s.io`.

        ***

        ```bash theme={null}
        # 6) Roles (namespace-scoped) with access to mutating webhooks
        kubectl get role --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(
                (.rules[]?
                  | select(
                      (.resources[]? | IN("mutatingwebhookconfigurations")) and
                      (.verbs[]? | IN("create","update","patch","delete","deletecollection","*"))
                    )
                )?
              )
            | .metadata.namespace + ":" + .metadata.name
          ' | sort -u
        ```

        **What to look for**

        * Again, expected result is usually **no Roles**.
        * Any Role in non-admin namespaces indicates a likely problem.

        ***

        ```bash theme={null}
        # 7) Identify who is bound to risky ClusterRoles
        # Replace <CLUSTERROLE_NAME> with any role you identified as suspicious
        kubectl get clusterrolebinding -o json \
          | jq -r '
            .items[]
            | select(.roleRef.kind=="ClusterRole" and .roleRef.name=="<CLUSTERROLE_NAME>")
            | .metadata.name as $b
            | .subjects[]? 
            | [$b, .kind, .namespace // "", .name]
            | @tsv
          ' | column -t
        ```

        **What to look for**

        * Problematic if:
          * ServiceAccounts used by apps/CI/CD pipelines are bound.
          * End-user groups (e.g. `devs`, `qa`, `viewers`) are bound.
        * Prefer only tightly controlled admin groups or ops service accounts.

        ***

        ```bash theme={null}
        # 8) Identify who is bound to risky Roles (if any)
        # Replace <ROLE_NAME> and <NAMESPACE> from step 3/6 output
        kubectl get rolebinding -n <NAMESPACE> -o json \
          | jq -r '
            .items[]
            | select(.roleRef.kind=="Role" and .roleRef.name=="<ROLE_NAME>")
            | .metadata.name as $b
            | .subjects[]?
            | [$b, .kind, .namespace // "", .name]
            | @tsv
          ' | column -t
        ```

        **What to look for**

        * Any non-admin users or application service accounts bound to these Roles are likely misconfigured.

        ***

        **Interpreting problems overall**

        * A finding exists when:
          * Any non-admin or broad/shared roles provide `create`, `update`, `patch`, `delete`, `deletecollection`, or `*` on:
            * `validatingwebhookconfigurations`
            * `mutatingwebhookconfigurations`
          * And those roles are bound to:
            * Application/CI service accounts,
            * General developer/QA/user groups,
            * Or any subjects beyond a small, trusted administrative set.

        Deciding which roles/subjects must retain this access and how to scope or remove it is a policy decision and must be done manually.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Report Roles/ClusterRoles that can access validating/mutating webhook configurations
        # Run on: any machine with kubectl access and appropriate RBAC to list roles/bindings

        set -euo pipefail

        echo "=== Checking ClusterRoles with webhook configuration permissions ==="
        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | {
                name: .metadata.name,
                rules: .rules[]? 
                | select(.apiGroups[]? == "admissionregistration.k8s.io")
                | select(.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
              }
            | select(.rules != null)
            | .name as $name
            | .rules
            | [
                $name,
                (.verbs | join(",")),
                (.resources | join(",")),
                (.apiGroups | join(","))
              ]
            | @tsv
          ' 2>/dev/null \
          | awk 'BEGIN{FS="\t"; OFS="\t"; print "TYPE","NAME","VERBS","RESOURCES","APIGROUPS"} 
                 {print "ClusterRole",$0}'

        echo
        echo "=== Checking Roles with webhook configuration permissions (namespace-scoped) ==="
        kubectl get roles --all-namespaces -o json \
          | jq -r '
            .items[]
            | .metadata.namespace as $ns
            | .metadata.name as $name
            | {
                ns: $ns,
                name: $name,
                rules: .rules[]? 
                | select(.apiGroups[]? == "admissionregistration.k8s.io")
                | select(.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
              }
            | select(.rules != null)
            | .ns as $ns
            | .name as $name
            | .rules
            | [
                $ns,
                $name,
                (.verbs | join(",")),
                (.resources | join(",")),
                (.apiGroups | join(","))
              ]
            | @tsv
          ' 2>/dev/null \
          | awk 'BEGIN{FS="\t"; OFS="\t"; print "TYPE","NAMESPACE","NAME","VERBS","RESOURCES","APIGROUPS"} 
                 {print "Role",$0}'

        echo
        echo "=== Showing who can use these ClusterRoles (ClusterRoleBindings) ==="
        # Find ClusterRoles that touch webhook configs, then show their bindings
        bad_crs=$(kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | select(
                [.rules[]? 
                  | select(.apiGroups[]? == "admissionregistration.k8s.io")
                  | select(.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
                ] | length > 0
              )
            | .metadata.name
          ')

        if [ -n "${bad_crs}" ]; then
          kubectl get clusterrolebindings -o json \
            | jq -r --argjson bad "[$(printf '"%s",' ${bad_crs} | sed 's/,$//')]" '
                .items[]
                | . as $crb
                | select(.roleRef.kind == "ClusterRole" and (.roleRef.name as $rn | $bad | index($rn)))
                | (
                    "ClusterRoleBinding: " + .metadata.name,
                    "  RoleRef: " + .roleRef.kind + "/" + .roleRef.name,
                    "  Subjects:",
                    ( .subjects[]? 
                      | "    - " + (.kind // "") + "/" + (.namespace // "") + ":" + (.name // "")
                    ),
                    ""
                  )
              '
        else
          echo "No ClusterRoles with webhook configuration permissions found."
        fi

        echo
        echo "=== Showing who can use these Roles (RoleBindings) ==="
        # Find Roles that touch webhook configs, then show their bindings
        bad_roles=$(kubectl get roles --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(
                [.rules[]? 
                  | select(.apiGroups[]? == "admissionregistration.k8s.io")
                  | select(.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
                ] | length > 0
              )
            | [.metadata.namespace, .metadata.name]
            | @tsv
          ')

        if [ -n "${bad_roles}" ]; then
          # Build a jq filter that matches any (namespace,name) pair
          jq_filter='
            .items[]
            | . as $rb
            | select(.roleRef.kind == "Role")
            | .roleRef.name as $rname
            | .metadata.namespace as $rbns
          '
          # Post-filter in shell since jq can't easily take a big set of (ns,name) pairs
          kubectl get rolebindings --all-namespaces -o json \
            | jq -r "${jq_filter} | [\$rbns, \$rname] | @tsv" \
            | while IFS=$'\t' read -r rb_ns rb_role; do
                if printf '%s\n' "${bad_roles}" | grep -q -E "^${rb_ns}[[:space:]]+${rb_role}\$"; then
                  kubectl get rolebinding -n "${rb_ns}" "$(kubectl get rolebinding -n "${rb_ns}" -o json \
                    | jq -r --arg ns "${rb_ns}" --arg rn "${rb_role}" '
                        .items[]
                        | select(.roleRef.kind=="Role" and .roleRef.name==$rn and .metadata.namespace==$ns)
                        | .metadata.name
                      ')" -n "${rb_ns}" -o yaml
                fi
              done
        else
          echo "No Roles with webhook configuration permissions found."
        fi
        ```

        Interpretation guidance (what indicates a problem):

        * Any Role/ClusterRole listed with:
          * `RESOURCES` including `validatingwebhookconfigurations` or `mutatingwebhookconfigurations`, and
          * `VERBS` including `create`, `update`, `patch`, or `delete`
        * Especially problematic if the associated (Cluster)RoleBindings show:
          * `system:authenticated`, `system:serviceaccounts`, or broad groups rather than a small set of named admin users/groups.
        * Acceptable patterns typically:
          * Are limited to a small, clearly-admin ClusterRole (e.g. `cluster-admin` in some environments) bound only to a trusted admin group, or
          * Are used by tightly-controlled CI/CD or operator service accounts with a justified need.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
