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 all RoleBindings and ClusterRoleBindings that reference system:anonymous or system:unauthenticated (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 binding you plan to remove, inspect it to understand what access it grants and whether an authenticated subject (e.g., specific user/group/service account) should replace it (run on any machine with kubectl access). Example for a namespaced RoleBinding:

    kubectl get rolebinding <ROLEBINDING_NAME> -n <NAMESPACE> -o yaml

    Example for a ClusterRoleBinding:

    kubectl get clusterrolebinding <CLUSTERROLEBINDING_NAME> -o yaml
  3. If you decide the binding is not required, delete the offending RoleBinding or ClusterRoleBinding (run on any machine with kubectl access). Example for a namespaced RoleBinding:

    kubectl delete rolebinding <ROLEBINDING_NAME> -n <NAMESPACE>

    Example for a ClusterRoleBinding:

    kubectl delete clusterrolebinding <CLUSTERROLEBINDING_NAME>
  4. If equivalent access is still needed, create or update a binding that grants the same Role/ClusterRole only to authenticated subjects (run on any machine with kubectl access). For example, to bind a ClusterRole to a specific group instead of system:unauthenticated:

    cat << 'EOF' | kubectl apply -f -
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
    name: <NEW_BINDING_NAME>
    subjects:
    - kind: Group
    name: <APPROVED_AUTHENTICATED_GROUP>
    apiGroup: rbac.authorization.k8s.io
    roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: ClusterRole
    name: <EXISTING_CLUSTERROLE_NAME>
    EOF
  5. Repeat steps 2–4 until all RoleBindings and ClusterRoleBindings that reference system:anonymous or system:unauthenticated have been deleted or appropriately replaced.

  6. Verify compliance (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'

    The output should be:

    is_compliant=true
Using kubectl

On any machine with kubectl access:

  1. Identify violating RoleBindings / 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)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
  1. For each violating RoleBinding, delete it
  • Namespaced RoleBinding (replace <namespace> and <name>):
kubectl delete rolebinding <name> -n <namespace>

Example:

kubectl delete rolebinding public-read -n default
  1. For each violating ClusterRoleBinding, delete it
  • Cluster-wide ClusterRoleBinding (replace <name>):
kubectl delete clusterrolebinding <name>

Example:

kubectl delete clusterrolebinding unauthenticated-view
  1. Re-apply any required access using authenticated subjects only (optional, declarative)

Create a manifest file (for example, rb-authenticated-only.yaml) with safe subjects, then apply:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: authenticated-view
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:authenticated

Apply it:

kubectl apply -f rb-authenticated-only.yaml
  1. Verification (must return 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'
Automation
#!/usr/bin/env bash
#
# Remediation: Delete any RoleBinding or ClusterRoleBinding that grants access
# to subjects named system:anonymous or system:unauthenticated.
#
# Requirements:
# - Run from any machine with kubectl access and current kube-context set to the OKE cluster.
# - kubectl and jq must be installed and on PATH.

set -euo pipefail

echo "[INFO] Discovering RoleBindings and ClusterRoleBindings targeting system:anonymous or system:unauthenticated..."

# Capture current violations in a temporary file for idempotence and traceability
WORKDIR="$(mktemp -d)"
VIOLATIONS_JSON="${WORKDIR}/violations.json"

kubectl get rolebindings,clusterrolebindings --all-namespaces -o json > "${VIOLATIONS_JSON}"

# Extract unique binding identifiers (kind, namespace, name) that reference the bad subjects
mapfile -t BINDINGS_TO_DELETE < <(
jq -r '
.items[]
| .kind as $kind
| .metadata as $m
| .subjects // []
| map(select(.name == "system:anonymous" or .name == "system:unauthenticated"))
| select(length > 0)
| [
$kind,
($m.namespace // ""),
$m.name
]
| @tsv
' "${VIOLATIONS_JSON}" | sort -u
)

if [ "${#BINDINGS_TO_DELETE[@]}" -eq 0 ]; then
echo "[INFO] No RoleBindings or ClusterRoleBindings reference system:anonymous or system:unauthenticated. Cluster is already compliant."
else
echo "[INFO] The following bindings will be deleted:"
printf ' %s\n' "${BINDINGS_TO_DELETE[@]}"

for line in "${BINDINGS_TO_DELETE[@]}"; do
kind="$(echo "${line}" | awk '{print $1}')"
ns="$(echo "${line}" | awk '{print $2}')"
name="$(echo "${line}" | awk '{print $3}')"

# Determine kubectl delete command based on scope
if [ "${kind}" = "RoleBinding" ]; then
if [ -z "${ns}" ]; then
echo "[WARN] RoleBinding ${name} has no namespace; skipping (invalid object)."
continue
fi
echo "[INFO] Deleting RoleBinding ${name} in namespace ${ns}..."
kubectl delete rolebinding "${name}" -n "${ns}" --ignore-not-found
elif [ "${kind}" = "ClusterRoleBinding" ]; then
echo "[INFO] Deleting ClusterRoleBinding ${name}..."
kubectl delete clusterrolebinding "${name}" --ignore-not-found
else
echo "[WARN] Unknown kind ${kind} for ${name}; skipping."
fi
done
fi

echo "[INFO] Verifying compliance after remediation..."

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'

echo "[INFO] Cleanup temporary files..."
rm -rf "${WORKDIR}"

echo "[INFO] Remediation script completed."