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.