Skip to main content

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

Manual Steps
  1. On any machine with kubectl access, list all bindings to system:anonymous and 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.txt

    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)"
    ' | tee /tmp/anonymous-rolebindings.txt
  2. 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 details
    kubectl get clusterrolebinding <CLUSTER_ROLE_BINDING_NAME> -o yaml

    # Namespaced RoleBinding details
    kubectl get rolebinding <ROLE_BINDING_NAME> -n <NAMESPACE> -o yaml

    # Inspect the referenced Role/ClusterRole
    kubectl get clusterrole <CLUSTER_ROLE_NAME> -o yaml
    kubectl get role <ROLE_NAME> -n <NAMESPACE> -o yaml
  3. 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.

  4. 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 group
    kubectl create clusterrolebinding crb-authenticated-viewers \
    --clusterrole=<EXISTING_OR_NEW_LEAST_PRIVILEGE_CLUSTERROLE> \
    --group=<AUTHENTICATED_GROUP_NAME>

    # Example: namespaced binding
    kubectl create rolebinding rb-authenticated-viewers \
    --namespace <NAMESPACE> \
    --role=<EXISTING_OR_NEW_LEAST_PRIVILEGE_ROLE> \
    --group=<AUTHENTICATED_GROUP_NAME>
  5. 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 bindings
    kubectl delete clusterrolebinding <CLUSTER_ROLE_BINDING_NAME>

    # Delete namespaced bindings
    kubectl delete rolebinding <ROLE_BINDING_NAME> -n <NAMESPACE>
  6. 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:

  1. 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)"
'
  1. 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>
  1. (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
  1. 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."