Skip to main content

No RoleBinding Should Grant Access To Anonymous Or Unauthenticated Users

More Info:

Verifies no (Cluster)RoleBinding targets system:anonymous or system:unauthenticated. Such bindings grant access to unauthenticated callers.

Risk Level

Critical

Address

Security

Compliance Standards

  • Cloudanix Best Practice

Triage and Remediation

Remediation

Manual Steps
  1. List the offending RoleBindings (run on any machine with kubectl access):

    kubectl get rolebindings,clusterrolebindings --all-namespaces -o json | jq -r '
    [ .items[]
    | .kind as $kind | .apiVersion as $api | .metadata as $m
    | ((.subjects // [])[] | select(.name == "system:anonymous" or .name == "system:unauthenticated"))
    | "kind=\($kind)"
    + (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
    + " name=\($m.name) uid=\($m.uid)"
    + (if ($api // "") == "" then "" else " apiVersion=\($api)" end)
    + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
    + " subject=\(.name) is_compliant=false"
    ] as $rows
    | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
  2. For each violating namespaced RoleBinding you want to remove, delete it (on any machine with kubectl access), replacing <namespace> and <name> with values from step 1:

    kubectl delete rolebinding <name> -n <namespace>
  3. For each violating ClusterRoleBinding you want to remove, delete it (on any machine with kubectl access), replacing <name> with the value from step 1:

    kubectl delete clusterrolebinding <name>
  4. If you need to preserve access for specific authenticated identities, recreate appropriate bindings without system:anonymous or system:unauthenticated as subjects (on any machine with kubectl access). For example, bind a ClusterRole to a specific Azure AD group:

    kubectl apply -f - << 'EOF'
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
    name: example-aks-auth-group-binding
    subjects:
    - kind: Group
    name: 00000000-0000-0000-0000-000000000000 # Azure AD group object ID
    apiGroup: rbac.authorization.k8s.io
    roleRef:
    kind: ClusterRole
    name: view
    apiGroup: rbac.authorization.k8s.io
    EOF
  5. Re-run the verification command to confirm there are no remaining bindings that reference system:anonymous or system:unauthenticated (on any machine with kubectl access):

    kubectl get rolebindings,clusterrolebindings --all-namespaces -o json | jq -r '
    [ .items[]
    | .kind as $kind | .apiVersion as $api | .metadata as $m
    | ((.subjects // [])[] | select(.name == "system:anonymous" or .name == "system:unauthenticated"))
    | "kind=\($kind)"
    + (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
    + " name=\($m.name) uid=\($m.uid)"
    + (if ($api // "") == "" then "" else " apiVersion=\($api)" end)
    + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
    + " subject=\(.name) is_compliant=false"
    ] as $rows
    | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'

    The output must include only:

    is_compliant=true
Using kubectl

On any machine with kubectl access:

  1. Identify violating RoleBindings and ClusterRoleBindings
kubectl get rolebindings,clusterrolebindings --all-namespaces -o json | jq -r '
[ .items[]
| .kind as $kind | .apiVersion as $api | .metadata as $m
| ((.subjects // [])[] | select(.name == "system:anonymous" or .name == "system:unauthenticated"))
| "kind=\($kind)"
+ (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
+ " name=\($m.name) uid=\($m.uid)"
+ (if ($api // "") == "" then "" else " apiVersion=\($api)" end)
+ (if ($m.creationTimestamp // "") == "" else " created=\($m.creationTimestamp)" end)
+ " subject=\(.name) is_compliant=false"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'

Note each violating object’s kind, name, and, if present, ns= namespace.

  1. Delete each violating RoleBinding

For namespaced RoleBindings (lines with kind=RoleBinding ns=<namespace> name=<name>):

kubectl delete rolebinding <name> -n <namespace>

Example:

kubectl delete rolebinding public-read -n default
  1. Delete each violating ClusterRoleBinding

For cluster-wide bindings (lines with kind=ClusterRoleBinding name=<name> and no ns=):

kubectl delete clusterrolebinding <name>

Example:

kubectl delete clusterrolebinding allow-anonymous-access
  1. (Optional, declarative) Remove from GitOps/manifests

If these bindings are managed declaratively (e.g., Helm, Kustomize, or IaC), remove the offending RoleBinding / ClusterRoleBinding definitions from the source manifests and re-apply:

kubectl apply -f <your-cleaned-manifest>.yaml
  1. Verify remediation

Re-run the audit command; it should return only is_compliant=true:

kubectl get rolebindings,clusterrolebindings --all-namespaces -o json | jq -r '
[ .items[]
| .kind as $kind | .apiVersion as $api | .metadata as $m
| ((.subjects // [])[] | select(.name == "system:anonymous" or .name == "system:unauthenticated"))
| "kind=\($kind)"
+ (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
+ " name=\($m.name) uid=\($m.uid)"
+ (if ($api // "") == "" then "" else " apiVersion=\($api)" end)
+ (if ($m.creationTimestamp // "") == "" else " created=\($m.creationTimestamp)" end)
+ " subject=\(.name) is_compliant=false"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
#
# Automation for CBP C2.4 on AKS:
# Delete any RoleBinding/ClusterRoleBinding that grants access to
# system:anonymous or system:unauthenticated.
#
# Run on: any machine with kubectl access and jq installed.
# Safe to re-run (idempotent).

set -euo pipefail

echo "=== CBP C2.4 remediation: removing RoleBindings/ClusterRoleBindings with anonymous/unauthenticated subjects ==="

# Ensure requirements
command -v kubectl >/dev/null 2>&1 || { echo "kubectl not found in PATH"; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq not found in PATH"; exit 1; }

# Get all violating bindings (kind, namespace, name) in a parseable form
violations_json="$(kubectl get rolebindings,clusterrolebindings --all-namespaces -o json | jq -c '
.items[]
| .kind as $kind
| .metadata.name as $name
| (.metadata.namespace // "") as $ns
| ((.subjects // [])[]
| select(.name == "system:anonymous" or .name == "system:unauthenticated")
) as $subject
| {
kind: $kind,
namespace: $ns,
name: $name,
subject: $subject.name
}
' || true)"

if [[ -z "$violations_json" ]]; then
echo "No rolebindings/clusterrolebindings with anonymous/unauthenticated subjects found."
else
echo "Found the following bindings granting access to anonymous/unauthenticated subjects:"
echo "$violations_json" | jq -r '. | "\(.kind) ns=\(.namespace) name=\(.name) subject=\(.subject)"' | sort -u

# Deduplicate by kind/namespace/name to avoid deleting the same object multiple times
echo
echo "Deleting violating bindings..."
echo "$violations_json" | jq -r '
{kind, namespace, name}
| @tsv
' | sort -u | while IFS=$'\t' read -r kind namespace name; do
if [[ -z "$name" ]]; then
continue
fi
if [[ "$kind" == "ClusterRoleBinding" ]]; then
echo "kubectl delete clusterrolebinding ${name}"
kubectl delete clusterrolebinding "${name}" --ignore-not-found
elif [[ "$kind" == "RoleBinding" ]]; then
if [[ -z "$namespace" ]]; then
# Should not occur for RoleBinding, but guard anyway
echo "Skipping RoleBinding ${name} with empty namespace"
continue
fi
echo "kubectl delete rolebinding ${name} -n ${namespace}"
kubectl delete rolebinding "${name}" -n "${namespace}" --ignore-not-found
fi
done
fi

echo
echo "=== Verification (should print only 'is_compliant=true') ==="
kubectl get rolebindings,clusterrolebindings --all-namespaces -o json | jq -r '
[ .items[]
| .kind as $kind | .apiVersion as $api | .metadata as $m
| ((.subjects // [])[] | select(.name == "system:anonymous" or .name == "system:unauthenticated"))
| "kind=\($kind)"
+ (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
+ " name=\($m.name) uid=\($m.uid)"
+ (if ($api // "") == "" then "" else " apiVersion=\($api)" end)
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ " subject=\(.name) is_compliant=false"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'