Avoid Bindings To System:anonymous
More Info:
ClusterRoleBindings or RoleBindings to the user system:anonymous grant permissions to unauthenticated callers. Such bindings should be removed or replaced with authenticated, least-privilege bindings.
Risk Level
Critical
Address
Security
Compliance Standards
- CIS GKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On any machine with kubectl access, list all bindings to
system:anonymousand capture them for review:kubectl get clusterrolebindings -o json | jq -r '.items[]| select((.subjects | length) > 0)| select(any(.subjects[]?;.kind=="User" and .name=="system:anonymous"))| "ClusterRoleBinding:\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"' | tee /tmp/anonymous-clusterrolebindings.txtkubectl get rolebindings -A -o json | jq -r '.items[]| select((.subjects | length) > 0)| select(any(.subjects[]?;.kind=="User" and .name=="system:anonymous"))| "RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"' | tee /tmp/anonymous-rolebindings.txt -
For each identified binding, inspect its full specification and permissions to understand what it allows and whether it is still needed. Replace the placeholder names with actual values from step 1:
# ClusterRoleBinding detailskubectl get clusterrolebinding <CLUSTER_ROLE_BINDING_NAME> -o yaml# Namespaced RoleBinding detailskubectl get rolebinding <ROLE_BINDING_NAME> -n <NAMESPACE> -o yaml# Inspect the referenced Role/ClusterRolekubectl get clusterrole <CLUSTER_ROLE_NAME> -o yamlkubectl get role <ROLE_NAME> -n <NAMESPACE> -o yaml -
Decide on safer access patterns based on your organization’s auth model. For each binding that must continue to exist in some form, choose or create an authenticated subject with least-privilege access (for example, a user-defined group like
my-anon-like-viewers) and ensure that group is actually used by your authentication system. Create such a group or user in your IdP or GKE IAM configuration as appropriate before proceeding. -
For each unsafe binding you will replace, create a new binding to the authenticated, least-privilege subject. Example (adjust names, namespace, and role according to your review):
# Example: replace anonymous cluster-wide access with an authenticated groupkubectl create clusterrolebinding crb-authenticated-viewers \--clusterrole=<EXISTING_OR_NEW_LEAST_PRIVILEGE_CLUSTERROLE> \--group=<AUTHENTICATED_GROUP_NAME># Example: namespaced bindingkubectl create rolebinding rb-authenticated-viewers \--namespace <NAMESPACE> \--role=<EXISTING_OR_NEW_LEAST_PRIVILEGE_ROLE> \--group=<AUTHENTICATED_GROUP_NAME> -
After confirming the new bindings work for intended users or groups, delete the unsafe bindings to
system:anonymous. Replace names/namespaces with those found in step 1:# Delete cluster-wide bindingskubectl delete clusterrolebinding <CLUSTER_ROLE_BINDING_NAME># Delete namespaced bindingskubectl delete rolebinding <ROLE_BINDING_NAME> -n <NAMESPACE> -
Verification (on any machine with kubectl access): re-run the audit to confirm no bindings remain to
system:anonymous:(kubectl get clusterrolebindings -o json | jq -r '.items[]| select((.subjects | length) > 0)| select(any(.subjects[]?;.kind=="User" and .name=="system:anonymous"))| "FOUND_ANONYMOUS:ClusterRoleBinding:\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"';kubectl get rolebindings -A -o json | jq -r '.items[]| select((.subjects | length) > 0)| select(any(.subjects[]?;.kind=="User" and .name=="system:anonymous"))| "FOUND_ANONYMOUS:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"') | (grep -q '^FOUND_ANONYMOUS:' && cat || echo 'NO_ANONYMOUS_BINDINGS')
Using kubectl
On any machine with kubectl access:
- List all bindings to
system:anonymous(for review)
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="User" and .name=="system:anonymous"
))
| "ClusterRoleBinding:\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
'
kubectl get rolebindings -A -o json | jq -r '
.items[]
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="User" and .name=="system:anonymous"
))
| "RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
'
- After deciding which bindings are unsafe and can be removed, delete them
Replace the placeholder names with the exact ones from step 1.
# Example for cluster-wide bindings
kubectl delete clusterrolebinding <CLUSTER_ROLE_BINDING_NAME_1>
kubectl delete clusterrolebinding <CLUSTER_ROLE_BINDING_NAME_2>
# Example for namespace-scoped bindings
kubectl delete rolebinding <ROLE_BINDING_NAME_1> -n <NAMESPACE_1>
kubectl delete rolebinding <ROLE_BINDING_NAME_2> -n <NAMESPACE_2>
- (Optional) Replace with least-privilege, authenticated bindings
Example: bind a specific group my-authenticated-users to an appropriate Role/ClusterRole you have defined.
ClusterRoleBinding example (cluster-wide):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: my-authenticated-users-view
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: Group
name: my-authenticated-users
apiGroup: rbac.authorization.k8s.io
Apply:
kubectl apply -f my-authenticated-users-view-clusterrolebinding.yaml
RoleBinding example (namespace-scoped):
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ns-readers
namespace: my-namespace
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ns-read-only
subjects:
- kind: Group
name: my-authenticated-users
apiGroup: rbac.authorization.k8s.io
Apply:
kubectl apply -f ns-readers-rolebinding.yaml
- Verification (same command as audit)
(
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="User" and .name=="system:anonymous"
))
| "FOUND_ANONYMOUS:ClusterRoleBinding:\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
';
kubectl get rolebindings -A -o json | jq -r '
.items[]
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="User" and .name=="system:anonymous"
))
| "FOUND_ANONYMOUS:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
'
) | (grep -q '^FOUND_ANONYMOUS:' && cat || echo 'NO_ANONYMOUS_BINDINGS')
Automation
#!/usr/bin/env bash
# Remediation for CIS GKE 4.1.8 – Remove bindings to user "system:anonymous"
# Runs on: any machine with kubectl, jq, and access to the cluster.
set -euo pipefail
# 1) Safety checks
if ! command -v kubectl >/dev/null 2>&1; then
echo "ERROR: kubectl not found in PATH" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "ERROR: jq not found in PATH" >&2
exit 1
fi
if ! kubectl version --request-timeout=5s >/dev/null 2>&1; then
echo "ERROR: Unable to talk to the Kubernetes API with current kubectl context" >&2
exit 1
fi
echo "=== Detecting ClusterRoleBindings and RoleBindings that target user \"system:anonymous\" ==="
# 2) Discover offending ClusterRoleBindings
mapfile -t CRB_TO_DELETE < <(
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select((.subjects // []) | length > 0)
| select(any(.subjects[]?;
.kind=="User" and .name=="system:anonymous"
))
| .metadata.name
'
)
# 3) Discover offending RoleBindings (name + namespace)
# Format: "<namespace> <name>"
mapfile -t RB_TO_DELETE < <(
kubectl get rolebindings -A -o json | jq -r '
.items[]
| select((.subjects // []) | length > 0)
| select(any(.subjects[]?;
.kind=="User" and .name=="system:anonymous"
))
| "\(.metadata.namespace) \(.metadata.name)"
'
)
if [[ ${#CRB_TO_DELETE[@]} -eq 0 && ${#RB_TO_DELETE[@]} -eq 0 ]]; then
echo "No bindings to user system:anonymous found. Nothing to do."
else
echo "The following ClusterRoleBindings reference user system:anonymous:"
for crb in "${CRB_TO_DELETE[@]}"; do
echo " ClusterRoleBinding: ${crb}"
done
echo
echo "The following RoleBindings reference user system:anonymous:"
for rb in "${RB_TO_DELETE[@]}"; do
ns="${rb%% *}"
name="${rb#* }"
echo " RoleBinding: ${name} (namespace: ${ns})"
done
echo
echo "WARNING: Deleting these bindings may impact unauthenticated access patterns."
echo "Ensure you have reviewed their necessity and implemented safer, authenticated alternatives if needed."
read -r -p "Type 'yes' to proceed with deletion, or anything else to abort: " CONFIRM
if [[ "${CONFIRM}" != "yes" ]]; then
echo "Aborting without changes."
exit 0
fi
# 4) Delete offending ClusterRoleBindings
if [[ ${#CRB_TO_DELETE[@]} -gt 0 ]]; then
echo "=== Deleting ClusterRoleBindings bound to system:anonymous ==="
for crb in "${CRB_TO_DELETE[@]}"; do
echo "Deleting ClusterRoleBinding/${crb} ..."
# Idempotent: if already gone, ignore error
kubectl delete clusterrolebinding "${crb}" --ignore-not-found
done
fi
# 5) Delete offending RoleBindings
if [[ ${#RB_TO_DELETE[@]} -gt 0 ]]; then
echo "=== Deleting RoleBindings bound to system:anonymous ==="
for rb in "${RB_TO_DELETE[@]}"; do
ns="${rb%% *}"
name="${rb#* }"
echo "Deleting RoleBinding/${name} in namespace ${ns} ..."
kubectl delete rolebinding "${name}" --namespace "${ns}" --ignore-not-found
done
fi
fi
echo
echo "=== Verification (CIS GKE 4.1.8 audit) ==="
(
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="User" and .name=="system:anonymous"
))
| "FOUND_ANONYMOUS:ClusterRoleBinding:\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
';
kubectl get rolebindings -A -o json | jq -r '
.items[]
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="User" and .name=="system:anonymous"
))
| "FOUND_ANONYMOUS:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
'
) | (grep -q '^FOUND_ANONYMOUS:' && cat || echo 'NO_ANONYMOUS_BINDINGS')
echo "Done."