Skip to main content

Minimize Access To Webhook Configuration Objects

More Info:

Access to validating or mutating webhook configuration objects can be abused to intercept or alter admission decisions. Restrict this access to trusted administrators only.

Risk Level

High

Address

Security

Compliance Standards

  • CIS EKS

Triage and Remediation

Remediation

Manual Steps
  1. List all cluster roles and role bindings that reference webhook configuration resources

    • Run on: any machine with kubectl access
    kubectl get clusterrole -o yaml | egrep -n 'name: (validatingwebhookconfigurations|mutatingwebhookconfigurations)' -A5 -B5
    kubectl get role -A -o yaml | egrep -n 'name: (validatingwebhookconfigurations|mutatingwebhookconfigurations)' -A5 -B5
    kubectl get clusterrolebinding -o yaml | egrep -n 'name: (validatingwebhookconfigurations|mutatingwebhookconfigurations)' -A5 -B5
    kubectl get rolebinding -A -o yaml | egrep -n 'name: (validatingwebhookconfigurations|mutatingwebhookconfigurations)' -A5 -B5

    Focus on rules where .resources includes validatingwebhookconfigurations or mutatingwebhookconfigurations.

  2. Identify subjects (users, groups, service accounts) with access to these resources

    • Run on: any machine with kubectl access
      For each ClusterRole or Role found in step 1, get the full object and its bindings:
    kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
    kubectl get clusterrolebinding -o yaml | grep -n "<CLUSTERROLE_NAME>" -A10 -B2

    kubectl get role -A | grep "<ROLE_NAME>"
    kubectl get role -n <NAMESPACE> <ROLE_NAME> -o yaml
    kubectl get rolebinding -n <NAMESPACE> -o yaml | grep -n "<ROLE_NAME>" -A10 -B2

    List all .subjects and categorize them as trusted administrators or non-admin workloads.

  3. Decide which access is justified and which should be removed or constrained

    • Review each subject that has get, list, watch, create, update, patch, or delete on the webhook configuration resources.
    • Treat write access (create/update/patch/delete) as highly sensitive; normally, only a very small admin group should retain this.
    • For non-admin service accounts or broad groups (e.g., system:authenticated), plan to remove webhook configuration permissions or replace them with narrower roles that do not include these resources.
  4. Remove or restrict webhook configuration access from inappropriate roles

    • Run on: any machine with kubectl access
      Edit each role/clusterrole that should no longer grant access:
    kubectl edit clusterrole <CLUSTERROLE_NAME>
    kubectl edit role -n <NAMESPACE> <ROLE_NAME>

    In the opened editor, either:

    • Remove validatingwebhookconfigurations and mutatingwebhookconfigurations from the resources list, or
    • Delete the entire rule block that references them, if it is not needed.
      Save and exit to apply changes.
  5. If necessary, rebind subjects to safer roles

    • Run on: any machine with kubectl access
      For workloads that legitimately needed some permissions but not webhook configuration access, bind them to an alternative role without those resources:
    kubectl create rolebinding <NEW_BINDING_NAME> \
    --clusterrole=<SAFER_CLUSTERROLE_NAME> \
    --serviceaccount=<NAMESPACE>:<SERVICEACCOUNT_NAME> \
    --namespace <NAMESPACE>

    Then remove the old binding:

    kubectl delete clusterrolebinding <OLD_BINDING_NAME>
    # or
    kubectl delete rolebinding -n <NAMESPACE> <OLD_BINDING_NAME>
  6. Verify that only trusted administrators retain webhook configuration access

    • Run on: any machine with kubectl access
      Re-run a focused RBAC review:
    kubectl get clusterrole -o yaml | egrep -n 'name: (validatingwebhookconfigurations|mutatingwebhookconfigurations)' -A5 -B5
    kubectl get role -A -o yaml | egrep -n 'name: (validatingwebhookconfigurations|mutatingwebhookconfigurations)' -A5 -B5

    For each remaining role, confirm via its bindings that only trusted admin users/groups/service accounts are subjects. If any non-admin subjects remain, repeat steps 4–5.

Using kubectl
# 1) List all ClusterRoles that can access admission webhook configurations
# Run on: any machine with kubectl access

kubectl get clusterroles -o json \
| jq -r '
.items[]
| select(
.rules[]
| select(
(.apiGroups[]? == "admissionregistration.k8s.io")
and
(.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
)
)
| .metadata.name
' | sort -u

Problem indication: Any ClusterRole in this list is capable of accessing/altering webhook configurations. Non-admin or generic roles here (e.g., roles used by apps, CI/CD, or controllers) are suspect.

# 2) Show detailed rules for those ClusterRoles
# Replace <clusterrole-name> with a name from the previous command

kubectl get clusterrole <clusterrole-name> -o yaml

Problem indication:
Rules that include:

  • apiGroups: ["admissionregistration.k8s.io"]
  • resources: ["validatingwebhookconfigurations"] and/or ["mutatingwebhookconfigurations"]
  • verbs such as create, update, patch, delete, or list/get/watch beyond what trusted admins require.
# 3) Identify who is bound to those ClusterRoles (ClusterRoleBindings)
# Run once to see all relevant ClusterRoleBindings

kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole")
| select(
.roleRef.name as $r
| $r | IN(
# Paste the ClusterRole names from step 1 inside this IN() list, e.g.:
"cluster-admin","admission-webhook-admin"
)
)
| .metadata.name + " -> " + .roleRef.name + " :: " +
( .subjects // [] | map(.kind + "/" + .name) | join(",") )
' | sort

Problem indication:
Bindings where the subjects include:

  • Broad groups (e.g., system:authenticated, system:serviceaccounts, system:serviceaccounts:<namespace>)
  • Application service accounts or CI/CD identities
  • External user groups not intended as cluster administrators
# 4) Inspect specific ClusterRoleBindings in detail
kubectl get clusterrolebinding <binding-name> -o yaml

Problem indication:
subjects that are not clearly limited to a small, trusted admin group or break-glass admin accounts.

# 5) Also check namespace-scoped Roles (less common but possible)
kubectl get roles --all-namespaces -o json \
| jq -r '
.items[]
| select(
.rules[]
| select(
(.apiGroups[]? == "admissionregistration.k8s.io")
and
(.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
)
)
| .metadata.namespace + "/" + .metadata.name
' | sort -u
# 6) For any Role found in step 5, see who is bound to it
kubectl get rolebinding -A -o json \
| jq -r '
.items[]
| .metadata.namespace as $ns
| .roleRef.kind as $k
| .roleRef.name as $r
| select($k == "Role")
| .metadata.name as $rb
| .subjects // [] as $subs
| "\($ns)/\($rb) -> \($k)/\($r) :: " + ($subs | map(.kind + "/" + .name) | join(","))
' | sort

Problem indication:
Any Role/RoleBinding that grants access to webhook configuration resources to non-admin service accounts or broad groups.

# 7) Quick verification after manual adjustments (re-run scoping checks)
# ClusterRoles
kubectl get clusterroles -o json \
| jq -r '
.items[]
| select(
.rules[]
| select(
(.apiGroups[]? == "admissionregistration.k8s.io")
and
(.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
)
)
| .metadata.name
' | sort -u

Desired state:
Only a very small, clearly administrative set of ClusterRoles (and corresponding bindings) remain with access to validatingwebhookconfigurations and mutatingwebhookconfigurations.

Automation
#!/usr/bin/env bash
# Report RBAC subjects with access to validating/mutating webhook configurations.
# Run on: any machine with kubectl access and current context pointing to target cluster.

set -euo pipefail

echo "=== Discovering verbs that grant access to webhook configuration resources ==="

# Collect all ClusterRoles and Roles that reference validating/mutating webhook configs
kubectl get clusterroles,roles -A -o json \
| jq -r '
.items[]
| {
kind: .kind,
namespace: (.metadata.namespace // ""),
name: .metadata.name,
rules: (.rules // [])
}
| . as $obj
| $obj.rules[]
| select(
(.resources // [])
| map(
(split("/") | .[0]) # strip possible apiGroup/resource forms
)
| flatten
| any(. == "validatingwebhookconfigurations" or . == "mutatingwebhookconfigurations")
)
| {
kind: $obj.kind,
namespace: $obj.namespace,
name: $obj.name,
apiGroups: (.apiGroups // []),
resources: (.resources // []),
verbs: (.verbs // [])
}
| @json' | sort | uniq > /tmp/webhook-access-roles.json

if [[ ! -s /tmp/webhook-access-roles.json ]]; then
echo "No Roles or ClusterRoles directly reference validating or mutating webhook configurations."
exit 0
fi

echo
echo "=== Roles/ClusterRoles with webhook configuration access ==="
cat /tmp/webhook-access-roles.json | jq -r '
. as $r
| "\($r.kind)\t\($r.namespace)\t\($r.name)\tverbs=\($r.verbs|join(","))\tresources=\($r.resources|join(","))"
' | column -t

echo
echo "=== Finding RoleBindings/ClusterRoleBindings that grant these roles ==="

# Build a list of role references of interest
role_filters=$(cat /tmp/webhook-access-roles.json | jq -r '
. as $r
| if $r.kind == "ClusterRole" then
"(.kind==\"ClusterRoleBinding\" and .roleRef.kind==\"ClusterRole\" and .roleRef.name==\"\($r.name)\")"
else
"(.kind==\"RoleBinding\" and .roleRef.kind==\"Role\" and .roleRef.name==\"\($r.name)\" and .metadata.namespace==\"\($r.namespace)\")"
end
' | sort -u | paste -sd " or " -)

# If there are no role filters, exit
if [[ -z "${role_filters}" ]]; then
echo "No bindings found for the identified Roles/ClusterRoles."
exit 0
fi

kubectl get clusterrolebindings,rolebindings -A -o json \
| jq -r "
.items[]
| select(${role_filters})
| {
kind: .kind,
namespace: (.metadata.namespace // \"\"),
name: .metadata.name,
roleKind: .roleRef.kind,
roleName: .roleRef.name,
subjects: (.subjects // [])
}
| @json" > /tmp/webhook-access-bindings.json

if [[ ! -s /tmp/webhook-access-bindings.json ]]; then
echo "No RoleBindings or ClusterRoleBindings grant the webhook-accessing roles."
exit 0
fi

echo
echo "=== Bindings granting access to webhook configuration objects ==="
cat /tmp/webhook-access-bindings.json | jq -r '
. as $b
| if ($b.subjects | length) == 0 then
"\($b.kind)\t\($b.namespace)\t\($b.name)\trole=\($b.roleKind)/\($b.roleName)\tsubject=<none>"
else
$b.subjects[]
| "\($b.kind)\t\($b.namespace)\t\($b.name)\trole=\($b.roleKind)/\($b.roleName)\tsubject=\(.kind):\(.namespace // "-"):\(.name)"
end
' | column -t

echo
echo "=== Interpretation guidance ==="
cat <<'EOF'
Any subject listed above (User, Group, or ServiceAccount) has RBAC-granted access
to validatingwebhookconfigurations or mutatingwebhookconfigurations.

Potential problems to investigate:
- Non-admin users or broad groups (e.g., "system:authenticated", "system:serviceaccounts")
- Application-specific service accounts that should not control cluster-wide admission
- Wildcard verbs (*) or powerful verbs (create, update, patch, delete) on these resources
- ClusterRoleBindings that expose admin-like ClusterRoles to many subjects

Review each binding and decide whether the subject truly needs this level of access.
If not, update or remove the corresponding Role/ClusterRole or Binding.
EOF

Problematic output indicators:

  • Roles/ClusterRoles with verbs including create, update, patch, delete, or * on validatingwebhookconfigurations or mutatingwebhookconfigurations.
  • Bindings where:
    • subjects include broad groups like system:authenticated, system:unauthenticated, or generic team groups.
    • Application/service-specific ServiceAccount subjects appear, rather than trusted admin identities. These require manual review and possible RBAC tightening; they cannot be safely auto-fixed.