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
Remediation​
Manual Steps
-
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)"' -
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 yamlkubectl get role ns1-reader -n ns1 -o yaml -
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.
- Identify or create an authenticated, user-defined group (for example
-
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.yamlapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:name: safer-public-view-bindingroleRef:apiGroup: rbac.authorization.k8s.iokind: ClusterRolename: viewsubjects:- kind: Groupname: myorg-public-viewersapiGroup: rbac.authorization.k8s.ioEOFkubectl apply -f safer-public-view-binding.yamlAdjust names, kind, and namespace for RoleBinding cases:
cat << 'EOF' > safer-ns1-read-binding.yamlapiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:name: safer-ns1-read-bindingnamespace: ns1roleRef:apiGroup: rbac.authorization.k8s.iokind: Rolename: ns1-readersubjects:- kind: Groupname: myorg-ns1-readersapiGroup: rbac.authorization.k8s.ioEOFkubectl apply -f safer-ns1-read-binding.yaml -
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> -
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:
- 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
- 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>
- 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>
- (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
- 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