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​
- Remediation
Remediation​
Manual Steps
-
List all Roles/ClusterRoles with webhook configuration permissions
Run on: any machine with kubectl accesskubectl get clusterroles -o json | jq -r '.items[]| select((.rules[]? | .resources[]? | contains("validatingwebhookconfigurations"))or(.rules[]? | .resources[]? | contains("mutatingwebhookconfigurations")))| .metadata.name' | sort -ukubectl get roles -A -o json | jq -r '.items[]| select((.rules[]? | .resources[]? | contains("validatingwebhookconfigurations"))or(.rules[]? | .resources[]? | contains("mutatingwebhookconfigurations")))| "\(.metadata.namespace)/\(.metadata.name)"' | sort -u -
Inspect the exact permissions on each identified Role/ClusterRole
Run on: any machine with kubectl access
For each ClusterRole name from step 1:kubectl get clusterrole <CLUSTERROLE_NAME> -o yamlFor each namespaced Role from step 1:
kubectl get role -n <NAMESPACE> <ROLE_NAME> -o yamlReview
rulesfor verbs likecreate,update,patch, ordeleteonvalidatingwebhookconfigurationsormutatingwebhookconfigurations. Flag any Role/ClusterRole not strictly needed for trusted cluster administration. -
Identify who is bound to these Roles/ClusterRoles
Run on: any machine with kubectl access
For each ClusterRole:kubectl get clusterrolebindings -o json | jq -r '.items[]| select(.roleRef.kind=="ClusterRole" and .roleRef.name=="<CLUSTERROLE_NAME>")| .metadata.name'For each Role:
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:
kubectl get clusterrolebinding <CRB_NAME> -o yamlkubectl get rolebinding -n <NAMESPACE> <RB_NAME> -o yaml -
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.
- Classify each subject (user, group, serviceaccount) as:
-
Adjust RBAC to remove or narrow write access
Run on: any machine with kubectl access
a) To remove an unnecessary binding entirely: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:
kubectl edit clusterrole <CLUSTERROLE_NAME>kubectl edit role -n <NAMESPACE> <ROLE_NAME>In the editor, remove
create,update,patch,deleteverbs or removevalidatingwebhookconfigurations/mutatingwebhookconfigurationsfromresourcesfor any non-admin use. Save to apply. -
Verify resulting access is limited to trusted administrators
Run on: any machine with kubectl access
Re-run the discovery and manually confirm bindings:kubectl get clusterroles -o json | jq -r '.items[]| select((.rules[]? | .resources[]? | contains("validatingwebhookconfigurations"))or(.rules[]? | .resources[]? | contains("mutatingwebhookconfigurations")))| .metadata.name' | sort -ukubectl get roles -A -o json | jq -r '.items[]| select((.rules[]? | .resources[]? | contains("validatingwebhookconfigurations"))or(.rules[]? | .resources[]? | contains("mutatingwebhookconfigurations")))| "\(.metadata.namespace)/\(.metadata.name)"' | sort -uFor the remaining Roles/ClusterRoles, confirm via their RoleBindings/ClusterRoleBindings that only trusted administrator identities retain write access to webhook configuration objects.
Using kubectl
# 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
ClusterRolenames. - 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.
- Generic or broad roles like
# 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:
resourcesincludesvalidatingwebhookconfigurationsandverbsincludes powerful verbs (create,update,patch,delete,deletecollection, or*).apiGroupsisadmissionregistration.k8s.iowith broad wildcard usage:resources: ["*"]orverbs: ["*"].
- Acceptable only for tightly controlled admin/cluster-operations roles.
# 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).
# 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
# 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
mutatingwebhookconfigurationsor wildcard rules inadmissionregistration.k8s.io.
# 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.
# 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.
# 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:validatingwebhookconfigurationsmutatingwebhookconfigurations
- And those roles are bound to:
- Application/CI service accounts,
- General developer/QA/user groups,
- Or any subjects beyond a small, trusted administrative set.
- Any non-admin or broad/shared roles provide
Deciding which roles/subjects must retain this access and how to scope or remove it is a policy decision and must be done manually.
Automation
#!/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:
RESOURCESincludingvalidatingwebhookconfigurationsormutatingwebhookconfigurations, andVERBSincludingcreate,update,patch, ordelete
- 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-adminin 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.
- Are limited to a small, clearly-admin ClusterRole (e.g.