Skip to main content

Minimize Wildcard Use Roles And ClusterRoles

More Info:​

Kubernetes Roles and ClusterRoles provide access to resources based on sets of objects and actions that can be taken on those objects. It is possible to set either of these to be the wildcard * which matches all items.

Risk Level​

Medium

Address​

Security

Compliance Standards​

  • CIS Kubernetes

Triage and Remediation​

Remediation​

Manual Steps
  1. Identify all noncompliant Roles and ClusterRoles (run on any machine with kubectl access):

    # Roles
    kubectl get roles --all-namespaces -o custom-columns=ROLE_NAMESPACE:.metadata.namespace,ROLE_NAME:.metadata.name --no-headers | \
    while read -r role_namespace role_name; do
    role_rules=$(kubectl get role -n "${role_namespace}" "${role_name}" -o=json | jq -c '.rules')
    if echo "${role_rules}" | grep -q "\[\"\*\"\]"; then
    echo "NONCOMPLIANT ROLE: ${role_namespace}/${role_name}"
    echo " rules: ${role_rules}"
    fi
    done

    # ClusterRoles
    kubectl get clusterroles -o custom-columns=CLUSTERROLE_NAME:.metadata.name --no-headers | \
    while read -r clusterrole_name; do
    clusterrole_rules=$(kubectl get clusterrole "${clusterrole_name}" -o=json | jq -c '.rules')
    if echo "${clusterrole_rules}" | grep -q "\[\"\*\"\]"; then
    echo "NONCOMPLIANT CLUSTERROLE: ${clusterrole_name}"
    echo " rules: ${clusterrole_rules}"
    fi
    done
  2. For each NONCOMPLIANT Role, review who uses it and what it truly needs (run on any machine with kubectl access):

    # Example: inspect bindings for a given role
    kubectl get rolebindings --all-namespaces -o yaml | \
    grep -A5 "name: <ROLE_NAME>" | sed -n '1,10p'

    # Show full definition of the role
    kubectl get role -n <ROLE_NAMESPACE> <ROLE_NAME> -o yaml

    Decide, based on actual application requirements, which resources, resourceNames, and verbs are needed instead of "*".

  3. Edit each NONCOMPLIANT Role to replace "*" with specific resources/verbs (run on any machine with kubectl access):

    kubectl edit role -n <ROLE_NAMESPACE> <ROLE_NAME>

    In the editor, locate rules: and replace fields like:

    resources: ["*"]
    verbs: ["*"]
    apiGroups: ["*"]

    with the minimal specific lists required, for example:

    apiGroups: [""]
    resources: ["pods", "services"]
    verbs: ["get", "list", "watch"]

    Save and exit to apply the changes.

  4. For each NONCOMPLIANT ClusterRole, review usage and scope (run on any machine with kubectl access):

    # See which subjects are bound to a ClusterRole
    kubectl get clusterrolebindings -o yaml | \
    grep -A5 "name: <CLUSTERROLE_NAME>" | sed -n '1,10p'

    # Show full definition
    kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml

    Decide the minimal apiGroups, resources, resourceNames, and verbs needed instead of "*".

  5. Edit each NONCOMPLIANT ClusterRole to remove wildcards where possible (run on any machine with kubectl access):

    kubectl edit clusterrole <CLUSTERROLE_NAME>

    In the editor, under rules:, replace any "*" values with specific items as in step 3. If a particular wildcard is truly required (e.g., an administrative role), document the justification outside the manifest for security review.

  6. Verify that Roles and ClusterRoles no longer contain wildcard-only lists (run on any machine with kubectl access):

    # Verify Roles
    kubectl get roles --all-namespaces -o custom-columns=ROLE_NAMESPACE:.metadata.namespace,ROLE_NAME:.metadata.name --no-headers | \
    while read -r role_namespace role_name; do
    role_rules=$(kubectl get role -n "${role_namespace}" "${role_name}" -o=json | jq -c '.rules')
    if echo "${role_rules}" | grep -q "\[\"\*\"\]"; then
    echo "STILL NONCOMPLIANT ROLE: ${role_namespace}/${role_name}"
    fi
    done

    # Verify ClusterRoles
    kubectl get clusterroles -o custom-columns=CLUSTERROLE_NAME:.metadata.name --no-headers | \
    while read -r clusterrole_name; do
    clusterrole_rules=$(kubectl get clusterrole "${clusterrole_name}" -o=json | jq -c '.rules')
    if echo "${clusterrole_rules}" | grep -q "\[\"\*\"\]"; then
    echo "STILL NONCOMPLIANT CLUSTERROLE: ${clusterrole_name}"
    fi
    done

    The configuration is compliant when no Roles or ClusterRoles you intend to harden are reported as still noncompliant.

Using kubectl

On any machine with kubectl access:

  1. Identify the non‑compliant Roles and ClusterRoles
# Roles with wildcards
kubectl get roles --all-namespaces -o json | jq -r '
.items[]
| select(.rules[]? | tostring | test("\\[\"\\*\"\\]"))
| "\(.metadata.namespace) \(.metadata.name)"
'

# ClusterRoles with wildcards
kubectl get clusterroles -o json | jq -r '
.items[]
| select(.rules[]? | tostring | test("\\[\"\\*\"\\]"))
| .metadata.name
'
  1. Export each non‑compliant object and edit it locally to remove "*" and replace with the minimum required verbs/resources (this is a manual, least‑privilege design decision):
# Example: export a Role
kubectl get role -n NAMESPACE ROLE_NAME -o yaml > role-ROLE_NAME.yaml

# Example: export a ClusterRole
kubectl get clusterrole CLUSTERROLE_NAME -o yaml > clusterrole-CLUSTERROLE_NAME.yaml
  1. In each exported YAML:
  • For rules[].verbs, replace - "*" with explicit verbs, e.g.:
rules:
- apiGroups: [""]
resources:
- pods
verbs:
- get
- list
- watch
  • For rules[].resources, replace - "*" with explicit resources, e.g.:
rules:
- apiGroups: ["apps"]
resources:
- deployments
- statefulsets
verbs:
- get
- list
- watch
- update
  • For rules[].apiGroups / rules[].resourceNames, similarly avoid "*" and specify concrete values when possible.
  1. Apply the edited manifests back to the cluster
# Role
kubectl apply -f role-ROLE_NAME.yaml

# ClusterRole
kubectl apply -f clusterrole-CLUSTERROLE_NAME.yaml

Repeat for each affected Role and ClusterRole.

  1. Verification

Re‑run the audit and confirm all role_is_compliant and clusterrole_is_compliant values are true:

# Roles
kubectl get roles --all-namespaces -o custom-columns=ROLE_NAMESPACE:.metadata.namespace,ROLE_NAME:.metadata.name --no-headers | while read -r role_namespace role_name
do
role_rules=$(kubectl get role -n "${role_namespace}" "${role_name}" -o=json | jq -c '.rules')
if echo "${role_rules}" | grep -q "\[\"\*\"\]"; then
role_is_compliant="false"
else
role_is_compliant="true"
fi
echo "**role_name: ${role_name} role_namespace: ${role_namespace} role_rules: ${role_rules} role_is_compliant: ${role_is_compliant}"
done

# ClusterRoles
kubectl get clusterroles -o custom-columns=CLUSTERROLE_NAME:.metadata.name --no-headers | while read -r clusterrole_name
do
clusterrole_rules=$(kubectl get clusterrole "${clusterrole_name}" -o=json | jq -c '.rules')
if echo "${clusterrole_rules}" | grep -q "\[\"\*\"\]"; then
clusterrole_is_compliant="false"
else
clusterrole_is_compliant="true"
fi
echo "**clusterrole_name: ${clusterrole_name} clusterrole_rules: ${clusterrole_rules} clusterrole_is_compliant: ${clusterrole_is_compliant}"
done
Automation
#!/usr/bin/env bash
#
# Purpose:
# Report and interactively remediate wildcard ("*") usage in Roles and ClusterRoles.
# This script does NOT auto-decide replacements; an operator must choose specific
# verbs/resources/fields per case, as required by the benchmark (MANUAL).
#
# Usage:
# Run on any machine with kubectl and jq installed and a kubeconfig context to the cluster.
#
# ./minimize-wildcards-rbac.sh
#
# Behavior:
# - Lists all Roles and ClusterRoles that contain ["*"] in .rules.
# - For each, prompts whether you want to edit it manually (opens in editor).
# - After edits, re-checks and prints a compliance report.
# - Safe to re-run; already-clean objects are skipped automatically.
#

set -euo pipefail

# Ensure required tools
for bin in kubectl jq; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "ERROR: $bin is required but not found in PATH" >&2
exit 1
fi
done

EDITOR_CMD="${EDITOR:-vi}"

echo "=== Scanning for wildcard usage in Roles and ClusterRoles ==="

non_compliant_found=false

############################################
# Function: scan_roles
############################################
scan_roles() {
echo
echo "--- Checking Roles ---"
kubectl get roles --all-namespaces -o json \
| jq -r '.items[] | [.metadata.namespace, .metadata.name, (.rules // [])] | @base64' \
| while read -r item_b64; do
_jq() { echo "$item_b64" | base64 --decode | jq -r "$1"; }
ns=$(_jq '.[0]')
name=$(_jq '.[1]')
rules_json=$(_jq '.[2]')

if echo "$rules_json" | grep -q '\["\*"\]'; then
non_compliant_found=true
echo "NON-COMPLIANT Role: namespace=${ns} name=${name}"
echo " Current rules snippet (truncated to 400 chars):"
echo " $(echo "$rules_json" | head -c 400)"
echo

while true; do
read -r -p "Edit this Role now to replace \"*\" with specific values? [y/N]: " ans
ans=${ans:-N}
case "$ans" in
[yY])
# open in editor using kubectl edit; user must replace wildcards manually
kubectl edit role "$name" -n "$ns"
break
;;
[nN])
echo " Skipping edit for Role ${ns}/${name}"
break
;;
*)
echo " Please answer y or n."
;;
esac
done
fi
done
}

############################################
# Function: scan_clusterroles
############################################
scan_clusterroles() {
echo
echo "--- Checking ClusterRoles ---"
kubectl get clusterroles -o json \
| jq -r '.items[] | [.metadata.name, (.rules // [])] | @base64' \
| while read -r item_b64; do
_jq() { echo "$item_b64" | base64 --decode | jq -r "$1"; }
name=$(_jq '.[0]')
rules_json=$(_jq '.[1]')

if echo "$rules_json" | grep -q '\["\*"\]'; then
non_compliant_found=true
echo "NON-COMPLIANT ClusterRole: name=${name}"
echo " Current rules snippet (truncated to 400 chars):"
echo " $(echo "$rules_json" | head -c 400)"
echo

while true; do
read -r -p "Edit this ClusterRole now to replace \"*\" with specific values? [y/N]: " ans
ans=${ans:-N}
case "$ans" in
[yY])
kubectl edit clusterrole "$name"
break
;;
[nN])
echo " Skipping edit for ClusterRole ${name}"
break
;;
*)
echo " Please answer y or n."
;;
esac
done
fi
done
}

scan_roles
scan_clusterroles

echo
echo "=== Post-remediation verification (CIS-style) ==="

# Verification for Roles
echo
echo "--- Verifying Roles ---"
role_non_compliant=false
kubectl get roles --all-namespaces -o custom-columns=ROLE_NAMESPACE:.metadata.namespace,ROLE_NAME:.metadata.name --no-headers \
| while read -r role_namespace role_name; do
role_rules=$(kubectl get role -n "${role_namespace}" "${role_name}" -o=json | jq -c '.rules')
if echo "${role_rules}" | grep -q '\["\*"\]'; then
role_is_compliant="false"
role_non_compliant=true
else
role_is_compliant="true"
fi
echo "**role_name: ${role_name} role_namespace: ${role_namespace} role_rules: ${role_rules} role_is_compliant: ${role_is_compliant}"
done

# Verification for ClusterRoles
echo
echo "--- Verifying ClusterRoles ---"
clusterrole_non_compliant=false
kubectl get clusterroles -o custom-columns=CLUSTERROLE_NAME:.metadata.name --no-headers \
| while read -r clusterrole_name; do
clusterrole_rules=$(kubectl get clusterrole "${clusterrole_name}" -o=json | jq -c '.rules')
if echo "${clusterrole_rules}" | grep -q '\["\*"\]'; then
clusterrole_is_compliant="false"
clusterrole_non_compliant=true
else
clusterrole_is_compliant="true"
fi
echo "**clusterrole_name: ${clusterrole_name} clusterrole_rules: ${clusterrole_rules} clusterrole_is_compliant: ${clusterrole_is_compliant}"
done

echo
echo "=== Summary ==="
if $non_compliant_found; then
echo "Some Roles or ClusterRoles contained wildcards. Review the verification output above:"
echo " - role_is_compliant / clusterrole_is_compliant should be \"true\""
echo " - If any are still \"false\", reopen them with:"
echo " kubectl edit role <name> -n <namespace>"
echo " kubectl edit clusterrole <name>"
else
echo "No Roles or ClusterRoles were found with wildcard-only entries [\"*\"]."
fi