Minimize Access To Webhook Configuration Objects
More Info:
Access to validating or mutating webhook configurations can be used to intercept or alter admission decisions. Restrict this access.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all ClusterRoles that grant webhook access
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 -uSave the output list of ClusterRole names for review.
-
Inspect each identified ClusterRole’s permissions
For each ClusterRole name from step 1:CLUSTERROLE_NAME="<clusterrole-name>"kubectl get clusterrole "${CLUSTERROLE_NAME}" -o yamlReview
.rulesand note which verbs (get,list,watch,create,update,patch,delete) are allowed onvalidatingwebhookconfigurationsormutatingwebhookconfigurations. -
Determine which subjects receive these ClusterRoles
For each ClusterRole name from step 1, find ClusterRoleBindings:CLUSTERROLE_NAME="<clusterrole-name>"kubectl get clusterrolebindings -o json \| jq -r --arg cr "${CLUSTERROLE_NAME}" '.items[]| select(.roleRef.kind == "ClusterRole" and .roleRef.name == $cr)| .metadata.name'For each returned ClusterRoleBinding:
CRB_NAME="<clusterrolebinding-name>"kubectl get clusterrolebinding "${CRB_NAME}" -o yamlReview
.subjects(users, groups, service accounts) and confirm which ones truly need webhook configuration access. -
Decide least-privilege adjustments
For each ClusterRole and its bindings:- Confirm whether any subject actually needs to read or modify webhook configurations (e.g., admission controller operators).
- If only read is required, plan to remove
create,update,patch,delete. - If only specific webhook type is required, plan to remove access to the other (
validatingvsmutating). - If a subject does not need any webhook access, plan to:
- Rebind it to a different, less-privileged ClusterRole, or
- Remove the ClusterRoleBinding if not needed.
-
Apply targeted changes to ClusterRoles/ClusterRoleBindings
For each ClusterRole that needs reduction:kubectl edit clusterrole "<clusterrole-name>"In the editor, under
rulesforadmissionregistration.k8s.io, remove unnecessary verbs and/or resources, following your decisions from step 4, then save.
For each ClusterRoleBinding that should no longer grant webhook access:kubectl edit clusterrolebinding "<clusterrolebinding-name>"Adjust
.roleRefto a less-privileged ClusterRole or remove unneeded subjects; if the binding is not needed at all, delete it:kubectl delete clusterrolebinding "<clusterrolebinding-name>" -
Re-verify effective access after changes
Re-run the identification command from step 1:kubectl get clusterroles -o json \| jq -r '.items[]| select(.rules[]| select((.apiGroups[]? == "admissionregistration.k8s.io")and (.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))))| .metadata.name' | sort -uFor any remaining ClusterRoles, confirm via
kubectl get clusterrole <name> -o yamlthat only the minimal, justified permissions and bindings remain.
Using kubectl
# 1) List all ClusterRoles that can access validating or mutating webhook configurations
# Run on: any machine with kubectl access
kubectl get clusterrole -o json \
| jq -r '
.items[]
| select(
[
.rules[]
| select(
(.apiGroups[]? == "admissionregistration.k8s.io")
and (.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
)
] | length > 0
)
| .metadata.name
' | sort | uniq
Problem indication: Any non-system or broad-privilege role (e.g., custom app roles) listed here may be over-privileged and must be reviewed.
# 2) Show full details of ClusterRoles identified above
# Replace <CLUSTERROLE_NAME> with each name from step 1
kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
Problem indication: In the rules: section, look for:
apiGroups: ["admissionregistration.k8s.io"]resources: ["validatingwebhookconfigurations"]and/or["mutatingwebhookconfigurations"]verbsincludingcreate,update,patch,delete, or*
Roles that are:
- bound to many subjects,
- used by application service accounts,
- or grant write/delete (
create,update,patch,delete,*) are high risk and should be questioned.
# 3) Find which subjects (users/groups/serviceaccounts) are bound to those ClusterRoles
# Run for each ClusterRole of interest
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<CLUSTERROLE_NAME>")
| .metadata.name as $crb
| .subjects[]
| "\($crb)\t\(.kind)\t\(.namespace // "-")\t\(.name)"
' | column -t
Problem indication: Any binding that grants these roles to:
- generic groups (e.g.,
system:authenticated,system:unauthenticated), - broad human user groups (e.g.,
devs,adminsnot specifically for cluster ops), - application service accounts in non-system namespaces
suggests webhook configuration access is too widely granted.
# 4) Quick summary: show only roles with WRITE access to webhooks
kubectl get clusterrole -o json \
| jq -r '
.items[]
| select(
[
.rules[]
| select(
(.apiGroups[]? == "admissionregistration.k8s.io")
and (.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
and (.verbs[]? | IN("create","update","patch","delete","*"))
)
] | length > 0
)
| .metadata.name
' | sort | uniq
Problem indication: Any role in this list must have a clear, justified operational need; otherwise it is a candidate for permissions reduction.
# 5) Inspect direct access on the webhook objects themselves (cluster-wide)
# Read access is also sensitive (can expose configuration patterns).
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations
Problem indication: Large numbers of webhook configs, or unfamiliar names owned by third parties, warrant closer scrutiny of who manages them using steps 1–4.
Automation
#!/usr/bin/env bash
set -euo pipefail
# This script inspects which subjects can access validating/mutating webhook configurations.
# Run on any machine with kubectl access and cluster-wide RBAC permissions.
echo "=== 1) ClusterRoles with access to validatingwebhookconfigurations or mutatingwebhookconfigurations ==="
kubectl get clusterrole -o json \
| jq -r '
.items[]
| {
name: .metadata.name,
rules: (
.rules // []
| map(
select(
(.apiGroups[]? == "admissionregistration.k8s.io")
and (.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
)
)
)
}
| select((.rules | length) > 0)
| . as $r
| "ClusterRole: \($r.name)\nRules:",
(
$r.rules[]
| " apiGroups: \(.apiGroups // [])",
" resources: \(.resources // [])",
" verbs: \(.verbs // [])",
""
)' \
| sed 's/"//g' || echo "Failed to query ClusterRoles"
echo
echo "=== 2) ClusterRoleBindings mapping those ClusterRoles to subjects ==="
problematic_crs=$(kubectl get clusterrole -o json \
| jq -r '
.items[]
| select(
(.rules // [])
| map(
select(
(.apiGroups[]? == "admissionregistration.k8s.io")
and (.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
)
)
| length > 0
)
| .metadata.name
')
if [ -z "$problematic_crs" ]; then
echo "No ClusterRoles found with direct access to webhook configuration objects."
else
for cr in $problematic_crs; do
echo "ClusterRole: ${cr}"
kubectl get clusterrolebinding -o json \
| jq -r --arg CR "$cr" '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == $CR)
| " ClusterRoleBinding: \(.metadata.name)",
" Subjects:",
(
.subjects // []
| .[]
| " kind=\(.kind) name=\(.name) namespace=\(.namespace // "-")"
),
""
'
echo
done
fi
echo "=== 3) (Optional) Namespaced Roles with access (less common) ==="
kubectl get role -A -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
name: .metadata.name,
rules: (
.rules // []
| map(
select(
(.apiGroups[]? == "admissionregistration.k8s.io")
and (.resources[]? | IN("validatingwebhookconfigurations","mutatingwebhookconfigurations"))
)
)
)
}
| select((.rules | length) > 0)
| . as $r
| "Role: \($r.ns)/\($r.name)\nRules:",
(
$r.rules[]
| " apiGroups: \(.apiGroups // [])",
" resources: \(.resources // [])",
" verbs: \(.verbs // [])",
""
)' \
| sed 's/"//g' || echo "Failed to query Roles"
cat <<'EOF'
How to interpret this output:
- Any ClusterRole or Role listed above that grants non-read-only verbs on:
resources: ["validatingwebhookconfigurations","mutatingwebhookconfigurations"]
apiGroups: ["admissionregistration.k8s.io"]
is potentially problematic.
- Pay particular attention to:
- verbs including: ["create","update","patch","delete","deletecollection","*"]
- broad roles like "cluster-admin" or custom admin/devops roles
- ClusterRoleBindings/RoleBindings that attach these roles to:
* system:authenticated or system:serviceaccounts
* groups or service accounts not strictly required to manage admission webhooks
- Safer patterns:
- No roles with these resources at all, except a very small number of tightly controlled
admin/SRE roles.
- If access is needed, limit verbs to ["get","list","watch"] where possible, and bind
only to specific trusted subjects.
Use this report to review and decide which roles/bindings should be tightened or removed.
EOF