Skip to main content

Avoid Non-Default Bindings To System:unauthenticated

More Info:​

Non-default bindings to the group system:unauthenticated grant permissions to any unauthenticated caller. They 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. List all non-default bindings to system:unauthenticated (run on any machine with kubectl access):

    kubectl get clusterrolebindings -o json | jq -r '
    .items[]
    | select(.metadata.name != "system:public-info-viewer")
    | select((.subjects | length) > 0)
    | select(any(.subjects[]?;
    .kind=="Group" and .name=="system:unauthenticated"
    ))
    | "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=="Group" and .name=="system:unauthenticated"
    ))
    | "RoleBinding \(.metadata.namespace)/\(.metadata.name): ROLE=\(.roleRef.kind)/\(.roleRef.name)"
    '
  2. For each listed binding, inspect its current permissions to understand the impact of removal (any machine with kubectl access):

    # Example for a ClusterRoleBinding named "public-view":
    kubectl get clusterrolebinding public-view -o yaml

    # Example for a RoleBinding "ns1/read-public" in namespace "ns1":
    kubectl get rolebinding read-public -n ns1 -o yaml

    # Inspect the referenced Role/ClusterRole to see granted permissions:
    # Suppose roleRef is ClusterRole "view" and Role "ns1-reader":
    kubectl get clusterrole view -o yaml
    kubectl get role ns1-reader -n ns1 -o yaml
  3. Decide whether each binding is necessary. For any binding that must remain functionally, design a safer alternative:

    • Identify or create an authenticated, user-defined group (for example myorg-public-viewers) outside the cluster.
    • Plan to bind that group to the same Role/ClusterRole instead of system:unauthenticated, and, if possible, reduce the role’s permissions to least privilege.
  4. Create replacement bindings using authenticated groups where needed (any machine with kubectl access). Example YAML for a replacement ClusterRoleBinding:

    cat << 'EOF' > safer-public-view-binding.yaml
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
    name: safer-public-view-binding
    roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: ClusterRole
    name: view
    subjects:
    - kind: Group
    name: myorg-public-viewers
    apiGroup: rbac.authorization.k8s.io
    EOF

    kubectl apply -f safer-public-view-binding.yaml

    Adjust names, kind, and namespace for RoleBinding cases:

    cat << 'EOF' > safer-ns1-read-binding.yaml
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
    name: safer-ns1-read-binding
    namespace: ns1
    roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: Role
    name: ns1-reader
    subjects:
    - kind: Group
    name: myorg-ns1-readers
    apiGroup: rbac.authorization.k8s.io
    EOF

    kubectl apply -f safer-ns1-read-binding.yaml
  5. After confirming that the replacement bindings satisfy your operational requirements (for example by testing access with an account in the new group), delete the unsafe bindings that reference system:unauthenticated (any machine with kubectl access):

    # Delete each offending ClusterRoleBinding (replace names with those from step 1):
    kubectl delete clusterrolebinding <UNSAFE_CLUSTERROLEBINDING_NAME>

    # Delete each offending RoleBinding (replace namespace/name with those from step 1):
    kubectl delete rolebinding <UNSAFE_ROLEBINDING_NAME> -n <NAMESPACE>
  6. Verification (any machine with kubectl access):

    (
    kubectl get clusterrolebindings -o json | jq -r '
    .items[]
    | select(.metadata.name != "system:public-info-viewer")
    | select((.subjects | length) > 0)
    | select(any(.subjects[]?;
    .kind=="Group" and .name=="system:unauthenticated"
    ))
    | "FOUND_UNAUTH: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=="Group" and .name=="system:unauthenticated"
    ))
    | "FOUND_UNAUTH:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
    '
    ) | (grep -q "^FOUND_UNAUTH:" && cat || echo "NO_NON_DEFAULT_UNAUTH_BINDINGS")

    Confirm that the output is:

    NO_NON_DEFAULT_UNAUTH_BINDINGS
Using kubectl

On any machine with kubectl access:

  1. List all non-default bindings to system:unauthenticated (review targets before deleting):
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.metadata.name != "system:public-info-viewer")
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="Group" and .name=="system:unauthenticated"
))
| .metadata.name
'

kubectl get rolebindings -A -o json | jq -r '
.items[]
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="Group" and .name=="system:unauthenticated"
))
| "\(.metadata.namespace) \(.metadata.name)"
'

Optionally inspect each binding and its permissions before removal, for example:

# Example for a ClusterRoleBinding
kubectl get clusterrolebinding <BINDING_NAME> -o yaml

# Example for a RoleBinding
kubectl get rolebinding <BINDING_NAME> -n <NAMESPACE> -o yaml
  1. Delete unsafe ClusterRoleBindings that bind system:unauthenticated (after review):
# Replace with each binding name you decide to remove
kubectl delete clusterrolebinding <CLUSTER_ROLE_BINDING_NAME_1>
kubectl delete clusterrolebinding <CLUSTER_ROLE_BINDING_NAME_2>
  1. Delete unsafe RoleBindings that bind system:unauthenticated (after review):
# Replace with each namespace/name pair you decide to remove
kubectl delete rolebinding <ROLE_BINDING_NAME_1> -n <NAMESPACE_1>
kubectl delete rolebinding <ROLE_BINDING_NAME_2> -n <NAMESPACE_2>
  1. (Optional) Create safer, authenticated bindings instead of system:unauthenticated:

Example manifest to replace a deleted ClusterRoleBinding, binding to a user-defined group:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: example-authenticated-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: my-authenticated-group # non-default, user-defined group

Apply it:

kubectl apply -f example-authenticated-binding.yaml
  1. Verification (rerun the benchmark audit command):
(
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.metadata.name != "system:public-info-viewer")
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="Group" and .name=="system:unauthenticated"
))
| "FOUND_UNAUTH: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=="Group" and .name=="system:unauthenticated"
))
| "FOUND_UNAUTH:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
'
) | (grep -q "^FOUND_UNAUTH:" && cat || echo "NO_NON_DEFAULT_UNAUTH_BINDINGS")
Automation
#!/usr/bin/env bash
set -euo pipefail

# This script removes all non-default RBAC bindings to the group "system:unauthenticated",
# except the default ClusterRoleBinding "system:public-info-viewer".
# Run on any machine with kubectl access and appropriate cluster-admin privileges.

echo "[INFO] Checking for non-default bindings to group system:unauthenticated..."

# Capture findings from the audit command
AUDIT_OUTPUT="$(
(
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.metadata.name != "system:public-info-viewer")
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="Group" and .name=="system:unauthenticated"
))
| "FOUND_UNAUTH: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=="Group" and .name=="system:unauthenticated"
))
| "FOUND_UNAUTH:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
'
) | (grep -q "^FOUND_UNAUTH:" && cat || echo "NO_NON_DEFAULT_UNAUTH_BINDINGS")
)"

echo "$AUDIT_OUTPUT"

if [[ "$AUDIT_OUTPUT" == "NO_NON_DEFAULT_UNAUTH_BINDINGS" ]]; then
echo "[INFO] No non-default bindings to system:unauthenticated found. Nothing to do."
exit 0
fi

# Parse and remove each offending binding
echo "[INFO] Removing non-default bindings to system:unauthenticated (after prior operator review)."

while IFS= read -r line; do
[[ -z "$line" ]] && continue
if [[ "$line" == NO_NON_DEFAULT_UNAUTH_BINDINGS* ]]; then
continue
fi

# Expected formats:
# FOUND_UNAUTH:ClusterRoleBinding:<name>:ROLE=<kind>/<roleName>
# FOUND_UNAUTH:RoleBinding:<namespace>:<name>:ROLE=<kind>/<roleName>
IFS=':' read -r _ prefix type_or_ns name rest <<< "$line"

if [[ "$line" == FOUND_UNAUTH:ClusterRoleBinding:* ]]; then
crb_name="$name"
echo "[INFO] Deleting ClusterRoleBinding ${crb_name}"
kubectl delete clusterrolebinding "${crb_name}" --ignore-not-found
elif [[ "$line" == FOUND_UNAUTH:RoleBinding:* ]]; then
rb_namespace="$type_or_ns"
rb_name="$name"
echo "[INFO] Deleting RoleBinding ${rb_namespace}/${rb_name}"
kubectl delete rolebinding "${rb_name}" --namespace "${rb_namespace}" --ignore-not-found
else
echo "[WARN] Unrecognized line format, skipping: $line"
fi
done <<< "$AUDIT_OUTPUT"

echo "[INFO] Re-running audit to verify remediation..."

VERIFY_OUTPUT="$(
(
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.metadata.name != "system:public-info-viewer")
| select((.subjects | length) > 0)
| select(any(.subjects[]?;
.kind=="Group" and .name=="system:unauthenticated"
))
| "FOUND_UNAUTH: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=="Group" and .name=="system:unauthenticated"
))
| "FOUND_UNAUTH:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)"
'
) | (grep -q "^FOUND_UNAUTH:" && cat || echo "NO_NON_DEFAULT_UNAUTH_BINDINGS")
)"

echo "$VERIFY_OUTPUT"

if [[ "$VERIFY_OUTPUT" == "NO_NON_DEFAULT_UNAUTH_BINDINGS" ]]; then
echo "[INFO] Verification succeeded: no non-default bindings to system:unauthenticated remain."
exit 0
else
echo "[ERROR] Verification failed: some non-default bindings to system:unauthenticated still exist."
exit 1
fi