Verify That Admission Controllers Are Working Expected
More Info:
Consider the use of an external secrets storage and management system, instead of using Kubernetes Secrets directly, if you have more complex secret management needs. Ensure the solution requires authentication to access secrets, has auditing of access to and use of secrets, and encrypts secrets. Some solutions also make it easier to rotate secrets.
Risk Level
Low
Address
Security
Compliance Standards
- CIS AKS
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify which admission webhooks control secret usage
- On any machine with kubectl access:
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations -A
- Review names and annotations to identify webhooks that enforce policies around Secrets (e.g., disallowing plaintext secrets, enforcing external secret references, enforcing labels/annotations on secret-using pods).
- On any machine with kubectl access:
-
Inspect webhook configuration and scope
- For each relevant webhook configuration from step 1:
kubectl get validatingwebhookconfiguration <NAME> -o yamlkubectl get mutatingwebhookconfiguration <NAME> -o yaml
- Manually verify:
rulesinclude the API groups/resources you rely on for secrets or secret references (e.g.,core/secrets, CRDs likeExternalSecret).namespaceSelector/objectSelectormatch the namespaces and objects where you expect enforcement.failurePolicymatches your risk appetite (e.g.,Failfor strict control-plane environments).
- For each relevant webhook configuration from step 1:
-
Confirm webhooks are reachable and healthy
- Check the services and pods backing those webhooks:
# Example: extract service/namespace from webhookClientConfig and inspectkubectl get svc -Akubectl get pods -A
- Manually confirm:
- The webhook service exists in the configured
namespaceandname. - Pods for the webhook service are
RunningandReady. - TLS config in
clientConfig(CA bundle, service, port, path) corresponds to the actual service.
- The webhook service exists in the configured
- Check the services and pods backing those webhooks:
-
Test enforcement using a controlled, non‑compliant object
- On any machine with kubectl access, create a test manifest that should be denied or mutated by your policy (e.g., a Secret or Pod violating your secret policy):
cat << 'EOF' > /tmp/test-secret.yamlapiVersion: v1kind: Secretmetadata:name: admission-test-secretnamespace: defaulttype: OpaquestringData:password: "plaintext-insecure"EOFkubectl apply -f /tmp/test-secret.yaml
- Observe whether the webhook denies, mutates, or allows it, and confirm that the behavior matches your intended policy for secret management.
- On any machine with kubectl access, create a test manifest that should be denied or mutated by your policy (e.g., a Secret or Pod violating your secret policy):
-
Review audit logs to confirm traceability of secret-related operations
- If API audit logging is enabled, use your log aggregation/search tool or, if logs are local on control-plane nodes, inspect them (example path, adjust to your environment):
# On a control-plane node, if API server writes local audit logssudo ls -l /var/log/kubernetes/sudo grep -E '"resource":"secrets"' /var/log/kubernetes/audit.log | head
- Manually verify that:
- Secret create/update/read events are logged with user identity and source.
- Webhook admission decisions appear as audit annotations if configured.
- If API audit logging is enabled, use your log aggregation/search tool or, if logs are local on control-plane nodes, inspect them (example path, adjust to your environment):
-
If behavior is incorrect, update webhook manifests and re‑test
- Edit the webhook configuration on any machine with kubectl access:
kubectl edit validatingwebhookconfiguration <NAME># orkubectl edit mutatingwebhookconfiguration <NAME>
- Adjust
rules, selectors,failurePolicy, orclientConfigto align with your intended secret‑management and access‑control behavior. - Re-run steps 2–4 to verify that admission controllers now enforce your expected policies on secret usage.
- Edit the webhook configuration on any machine with kubectl access:
Using kubectl
# 1) List all MutatingWebhookConfiguration objects
# Run on: any machine with kubectl access
kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io -A -o wide
- Potential problems:
- Webhooks you expect to be present are missing.
- Webhooks unexpectedly reference non-existent services or CABundles (STATUS column may be empty or errors in events).
# 2) List all ValidatingWebhookConfiguration objects
kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io -A -o wide
- Potential problems:
- Critical validating webhooks (e.g., for security policies, secret controls) are missing.
- Unexpected/unapproved webhooks exist (compare against your design/documents).
# 3) Inspect a specific webhook configuration in detail
# Replace <name> with an object from the get output above
kubectl get validatingwebhookconfiguration <name> -o yaml
Review in webhooks::
rules:– check that operations and resources match your intended coverage.- Problems: overly broad rules (e.g.
resources: ["*"]in all groups) or missing coverage for critical resources (e.g.,secrets,pods).
- Problems: overly broad rules (e.g.
namespaceSelector/objectSelector– ensure protected namespaces/workloads are not unintentionally excluded.failurePolicy–Ignorefor security-critical checks is usually a risk;Failmay be required by your policy.sideEffects– validate correctness for the implementation.clientConfig.service– confirmname,namespace, andpathpoint to an existing service (see next step).timeoutSeconds– too high can impact API performance; too low may cause frequent timeouts.
# 4) Verify the webhook service targets exist
# Replace <namespace> and <service> using values seen in clientConfig.service
kubectl -n <namespace> get svc <service> -o wide
kubectl -n <namespace> get pods -o wide
- Potential problems:
- Service not found.
- No backing pods, pods in CrashLoopBackOff, ImagePullBackOff, or NotReady.
- Selector mismatch between service and pods.
# 5) Check events and logs around failed or ignored admissions
kubectl get events -A --sort-by=.lastTimestamp | grep -i webhook
- Potential problems:
- Frequent “calling webhook failed” or “timed out” messages.
- Messages showing
failurePolicy: Ignoreleading to resources being admitted when the webhook is unavailable.
# 6) Probe that webhooks actually intercept requests
# Example: attempt an operation that SHOULD be blocked by policy
# (adjust to match your expected admission behavior)
kubectl run test-pod --image=nginx --dry-run=server -o yaml
Interpretation:
- If you expect a validating webhook to reject this kind of pod and the command succeeds without an admission error, the webhook may not be working as intended (rules, selectors, or failurePolicy misconfigured).
- If you see an explicit admission error from a webhook that matches your policy, that indicates it is active.
# 7) For webhooks managing secrets behavior, try a controlled test
# Adjust to an operation your policy should regulate, e.g., creating a Secret
kubectl create secret generic test-secret --from-literal=key=value --dry-run=server -o yaml
- Potential problems:
- Operation is admitted when your design requires a webhook decision (e.g., external secret manager integration, annotation enforcement).
- Error messages indicate misconfigured URL/service or certificate issues.
# 8) Confirm API server is configured to use admission plugins (cluster-info only)
kubectl cluster-info dump | grep -i admission -n
- Potential problems:
- Missing or unexpected admission plugins compared to your cluster’s security design (e.g., PSP/PSS, external secrets-related controllers, or custom admission plugins).
- Flags or config indicating disabled admission features you rely on.
Use these observations to decide whether current admission controllers and associated webhooks actually enforce your intended policies around secrets management and other security controls; any mismatch between expected and observed behavior indicates a problem that needs manual design and configuration changes.
Automation
#!/usr/bin/env bash
# Purpose: Cluster-wide report to help review that admission controllers and
# secret-handling policies are behaving as expected.
# Scope: Run on any machine with kubectl context to the target cluster.
set -euo pipefail
echo "=== 1) API Server admission configuration (Mutating/Validating configs) ==="
echo
echo "# MutatingWebhookConfiguration objects:"
kubectl get mutatingwebhookconfiguration -o wide || echo " (none found)"
echo
echo "# ValidatingWebhookConfiguration objects:"
kubectl get validatingwebhookconfiguration -o wide || echo " (none found)"
echo
echo "=== Detailed admission webhook configuration (for review) ==="
echo
kubectl get mutatingwebhookconfiguration,validatingwebhookconfiguration -o yaml | sed 's/^/ /' || \
echo " (no admission webhook configurations found)"
echo
cat <<'EOF'
[Review guidance: potential problems]
- No MutatingWebhookConfiguration/ValidatingWebhookConfiguration exist, even though
your design expects policy enforcement (e.g. Pod Security admission via webhooks,
custom policy engines, or secret-management webhooks).
- webhooks that *should* protect secret usage (e.g. reject plain Kubernetes Secrets)
are missing, disabled, or have `failurePolicy: Ignore` or `sideEffects: Unknown`.
- `namespaceSelector` / `objectSelector` excludes namespaces that should be protected
(e.g. workloads using external secret stores).
- `clientConfig` uses insecure endpoints (no TLS, or `insecureSkipTLSVerify: true`).
EOF
echo "=== 2) Workloads creating or using Kubernetes Secrets directly ==="
echo
echo "# Namespaces that contain Secret objects:"
kubectl get secret --all-namespaces --no-headers 2>/dev/null \
| awk '{print $1}' | sort -u | sed 's/^/ /' || echo " (no Secrets found)"
echo
echo "# Count of Secrets per namespace:"
kubectl get secret --all-namespaces --no-headers 2>/dev/null \
| awk '{count[$1]++} END {for (ns in count) printf "%s\t%d\n", ns, count[ns]}' \
| sort | sed 's/^/ /' || echo " (no Secrets found)"
echo
echo "# Pods that mount or reference Secrets (envFrom/env):"
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
volumes: (.spec.volumes // [] | map(select(.secret != null) | .secret.secretName) | unique),
envFrom: (
.spec.containers // []
| map(.envFrom // [] | map(select(.secretRef != null) | .secretRef.name) | .[])
),
env: (
.spec.containers // []
| map(.env // [] | map(select(.valueFrom.secretKeyRef != null) | .valueFrom.secretKeyRef.name) | .[])
)
}
| select((.volumes + .envFrom + .env) | length > 0)
| "\(.ns)\t\(.pod)\tvolumes:\(.volumes|join(\",\"))\tenvFrom:\(.envFrom|join(\",\"))\tenv:\(.env|join(\",\"))"
' 2>/dev/null | sed 's/^/ /' || echo " (no pods using Secrets found)"
echo
cat <<'EOF'
[Review guidance: potential problems]
- Business or security policy says workloads MUST use an external secret store,
but many Secrets and Secret-consuming Pods are present.
- Sensitive workloads are referencing plain `Secret` objects instead of provider-
specific CRDs for external secret managers (e.g. ExternalSecret, SecretProviderClass).
EOF
echo "=== 3) Evidence of external secret manager CRDs / controllers ==="
echo
echo "# Look for common external-secret CRDs:"
kubectl get crd 2>/dev/null | grep -iE 'secret|vault|external|provider' || \
echo " (no obvious secret-related CRDs found)"
echo
echo "# Controllers/Pods in namespaces that look like secret managers:"
kubectl get ns | grep -iE 'secret|vault|aws|azure|gcp|kms|hsm|key|crypto' || \
echo " (no obvious secret-related namespaces found)"
echo
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do
if echo "$ns" | grep -qiE 'secret|vault|aws|azure|gcp|kms|hsm|key|crypto'; then
echo "## Pods in namespace: $ns"
kubectl get pods -n "$ns" -o wide || true
echo
fi
done
cat <<'EOF'
[Review guidance: potential problems]
- Cluster is expected to use an external secrets system, but:
- No CRDs or controllers for that system are visible, or
- Relevant controller Pods are CrashLooping/NotReady.
EOF
echo "=== 4) Admission webhooks potentially involved in secrets/control ==="
echo
echo "# Webhooks whose names or rules reference secrets or policy:"
kubectl get mutatingwebhookconfiguration,validatingwebhookconfiguration -o json 2>/dev/null \
| jq -r '
.items[]
| . as $cfg
| .webhooks[]
| select(
(.name | test("secret|policy|vault|kms|compliance"; "i"))
or ([.rules[].resources[]?] | join(",") | test("secret"; "i"))
)
| "\($cfg.kind)/\($cfg.metadata.name)\t\( .name )"
' | sed 's/^/ /' || echo " (no obviously secret/policy-related webhooks found)"
echo
echo "# Full rules for those webhooks (for deeper review):"
kubectl get mutatingwebhookconfiguration,validatingwebhookconfiguration -o json 2>/dev/null \
| jq '
.items[]
| . as $cfg
| .webhooks[]
| select(
(.name | test("secret|policy|vault|kms|compliance"; "i"))
or ([.rules[].resources[]?] | join(",") | test("secret"; "i"))
)
| {
kind: $cfg.kind,
configName: $cfg.metadata.name,
webhookName: .name,
failurePolicy: .failurePolicy,
sideEffects: .sideEffects,
admissionReviewVersions: .admissionReviewVersions,
rules: .rules,
namespaceSelector: .namespaceSelector,
objectSelector: .objectSelector,
clientConfig: .clientConfig
}
' || echo " (no matching webhooks; skip)"
echo
cat <<'EOF'
[Review guidance: potential problems]
- Secret/policy-related webhooks exist but:
- `failurePolicy` is `Ignore` where `Fail` is required by your policy.
- `namespaceSelector`/`objectSelector` unintentionally bypasses protection.
- `clientConfig.service` points to non-existing Services/namespaces.
EOF
echo "=== 5) (Optional) Quick functional test of a basic admission path ==="
echo "# This creates a test Namespace and Pod, then deletes them."
TEST_NS="admission-test-$(date +%s)"
echo " Creating test namespace: $TEST_NS"
kubectl create namespace "$TEST_NS"
cat <<'POD' | kubectl apply -n "$TEST_NS" -f -
apiVersion: v1
kind: Pod
metadata:
name: admission-test-pod
spec:
containers:
- name: busybox
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
POD
echo " Pod created. Describe it to see if any mutating/validating webhooks acted on it:"
kubectl describe pod admission-test-pod -n "$TEST_NS" | sed 's/^/ /'
echo
echo " Cleaning up test resources..."
kubectl delete namespace "$TEST_NS" --wait=false
cat <<'EOF'
[Review guidance: potential problems]
- Expected mutating/validating webhooks (e.g. security policies, sidecar injection,
or secret-enforcement policies) do NOT appear in the Events/annotations of the
test Pod, even though they should apply to all Pods/namespaces.
EOF
echo "=== 6) Summary: what indicates a potential problem? ==="
cat <<'EOF'
- No admission webhooks are configured where your design expects them.
- Secret-related or policy-enforcing webhooks exist but are:
- Disabled, misconfigured, or set with failurePolicy=Ignore.
- Not targeting the namespaces/resources they are supposed to protect.
- Workloads are storing or consuming Kubernetes Secrets directly, despite a
policy to use an external secret manager.
- External secret manager components (CRDs/controllers) are missing or unhealthy.
- A simple test Pod shows no sign that expected admission controllers are invoked.
This script does NOT automatically fix anything. Use the above outputs to
decide whether admission controllers and secret-management policies are
behaving as designed, then adjust configurations manually as needed.
EOF