Skip to main content

Minimize Wildcard Use In Roles And ClusterRoles

More Info:

Wildcards in verbs, resources, or apiGroups grant overly broad permissions that violate least privilege. Replace them with explicitly scoped objects and actions.

Risk Level

Critical

Address

Security

Compliance Standards

  • CIS EKS

Triage and Remediation

Remediation

Manual Steps
  1. List Roles/ClusterRoles using wildcards (run on any machine with kubectl access):

    kubectl get roles --all-namespaces -o json | jq '
    .items[] | select(
    .rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
    ) | {kind, metadata:{name,namespace}}'
    kubectl get clusterroles -o json | jq '
    .items[] | select(
    .rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
    ) | {kind, metadata:{name}}'
  2. For each offending Role/ClusterRole, export its definition for review/edit (example for a ClusterRole named example-role):

    kubectl get clusterrole example-role -o yaml > /tmp/example-role.yaml
    # or for a namespaced Role
    kubectl get role example-role -n example-namespace -o yaml > /tmp/example-role.yaml
  3. Manually edit the exported file to replace wildcards with explicit values (run on the machine where the file is saved):

    • Replace verbs: ["*"] or verbs: - "*" with only the needed verbs, e.g.:
      verbs:
      - get
      - list
      - watch
    • Replace resources: ["*"] or resources: - "*" with specific resources, e.g.:
      resources:
      - pods
      - services
    • Replace apiGroups: ["*"] or apiGroups: - "*" with required API groups, e.g.:
      apiGroups:
      - ""
      - apps
    • Remove any rule entries that are not actually needed. Save the file.
  4. Apply the edited Role/ClusterRole back to the cluster (run on any machine with kubectl access):

    kubectl apply -f /tmp/example-role.yaml
  5. If these objects are managed by GitOps or other IaC, update the source manifests similarly (edit the Role/ClusterRole definitions in your Git/IaC repo to match the changes above and run your usual deployment pipeline) to prevent them from being reverted.

  6. Verify no Roles/ClusterRoles still use wildcards (run on any machine with kubectl access):

    wildcards=$(kubectl get roles --all-namespaces -o json | jq '
    .items[] | select(
    .rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
    )' | wc -l)

    wildcards_clusterroles=$(kubectl get clusterroles -o json | jq '
    .items[] | select(
    .rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
    )' | wc -l)

    total=$((wildcards + wildcards_clusterroles))
    if [ "$total" -eq 0 ]; then
    echo "no_wildcards_present"
    else
    echo "wildcards_present ($total)"
    fi
Using kubectl

On any machine with kubectl access:

  1. Identify ClusterRoles using wildcards
kubectl get clusterroles -o json | jq -r '
.items[]
| select(
.rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
)
| .metadata.name' \
| sort -u

For a single ClusterRole, inspect details:

kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
  1. Edit an existing ClusterRole to remove wildcards

For each listed ClusterRole, replace * in verbs, resources, and apiGroups with explicit entries based on actual needs.

Interactive edit:

kubectl edit clusterrole <CLUSTERROLE_NAME>

Example change (illustrative only; adjust to your requirements):

# BEFORE
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]

# AFTER
rules:
- apiGroups: [""] # core API group
resources:
- pods
- services
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- deployments
verbs:
- get
- list

Save and exit the editor to apply.

  1. Apply a declarative manifest (preferred for GitOps/IaC-managed roles)

If your ClusterRole is managed via manifests, update the manifest and re-apply it instead of using kubectl edit.

Example manifest snippet:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: <CLUSTERROLE_NAME>
rules:
- apiGroups: [""]
resources:
- pods
- configmaps
verbs:
- get
- list
- watch

Apply:

kubectl apply -f <path-to-updated-clusterrole-manifest>.yaml

Repeat for each ClusterRole using wildcards.

  1. Verification

Re-run the wildcard check to confirm there are no remaining wildcards in Roles or ClusterRoles:

wildcards=$(kubectl get roles --all-namespaces -o json | jq '
.items[] | select(
.rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
)' | wc -l)

wildcards_clusterroles=$(kubectl get clusterroles -o json | jq '
.items[] | select(
.rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
)' | wc -l)

total=$((wildcards + wildcards_clusterroles))

if [ "$total" -gt 0 ]; then
echo "wildcards_present"
else
echo "no_wildcards_in_roles_or_clusterroles"
fi
Automation
#!/usr/bin/env bash
# Automation for CISEKS 4.1.3 – Minimize wildcard use in ClusterRoles and Roles
# Run on: any machine with kubectl access and jq installed.
# WARNING: This script CANNOT safely auto-resolve wildcards without human input.
# It will:
# - Detect Roles/ClusterRoles using wildcards
# - Export them to YAML files for review
# - (Optionally) apply edited manifests
# - Re-run the audit to verify
# This is safe to re-run; it only overwrites the export directory and reapplies your edited YAML.

set -euo pipefail

EXPORT_DIR="${EXPORT_DIR:-./rbac-wildcards-export}"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
WORK_DIR="${EXPORT_DIR}/${TIMESTAMP}"

mkdir -p "${WORK_DIR}/roles" "${WORK_DIR}/clusterroles"

echo "=== [1/4] Discovering Roles with wildcards ==="
kubectl get roles --all-namespaces -o json | jq -c '
.items[]
| select(
.rules[]? |
(.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
)
| {ns: .metadata.namespace, name: .metadata.name}
' | while read -r item; do
ns=$(echo "${item}" | jq -r '.ns')
name=$(echo "${item}" | jq -r '.name')
echo "Exporting Role ${ns}/${name}"
kubectl get role "${name}" -n "${ns}" -o yaml > "${WORK_DIR}/roles/${ns}__${name}.yaml"
done

echo "=== [2/4] Discovering ClusterRoles with wildcards ==="
kubectl get clusterroles -o json | jq -c '
.items[]
| select(
.rules[]? |
(.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
)
| {name: .metadata.name}
' | while read -r item; do
name=$(echo "${item}" | jq -r '.name')
echo "Exporting ClusterRole ${name}"
kubectl get clusterrole "${name}" -o yaml > "${WORK_DIR}/clusterroles/${name}.yaml"
done

echo
echo "=== Manual remediation required ==="
echo "Directory with exported RBAC objects: ${WORK_DIR}"
echo "For each YAML file:"
echo " - Edit .rules[*].verbs, .resources, and .apiGroups"
echo " - Replace any \"*\" with the minimal explicit values needed."
echo
echo "Example (conceptual only):"
echo " verbs: [\"*\"] -> verbs: [\"get\", \"list\"]"
echo " resources: [\"*\"] -> resources: [\"pods\", \"pods/log\"]"
echo " apiGroups: [\"*\"] -> apiGroups: [\"\", \"apps\"]"
echo
echo "Do NOT change the metadata.name or metadata.namespace fields."

read -rp "Have you finished editing YAML files under ${WORK_DIR} and want to apply them now? (y/N): " APPLY_ANSWER
APPLY_ANSWER=${APPLY_ANSWER:-n}

if [[ "${APPLY_ANSWER}" =~ ^[Yy]$ ]]; then
echo "=== [3/4] Applying edited Roles ==="
if compgen -G "${WORK_DIR}/roles/*.yaml" > /dev/null; then
for f in "${WORK_DIR}/roles/"*.yaml; do
echo "Applying Role from ${f}"
kubectl apply -f "${f}"
done
else
echo "No Role YAML files found to apply."
fi

echo "=== [3/4] Applying edited ClusterRoles ==="
if compgen -G "${WORK_DIR}/clusterroles/*.yaml" > /dev/null; then
for f in "${WORK_DIR}/clusterroles/"*.yaml; do
echo "Applying ClusterRole from ${f}"
kubectl apply -f "${f}"
done
else
echo "No ClusterRole YAML files found to apply."
fi
else
echo "Skipping kubectl apply. You can manually run:"
echo " kubectl apply -f ${WORK_DIR}/roles/"
echo " kubectl apply -f ${WORK_DIR}/clusterroles/"
fi

echo
echo "=== [4/4] Verification (re-running wildcard audit) ==="
wildcards=$(kubectl get roles --all-namespaces -o json | jq '
.items[] | select(
.rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
)' | wc -l | tr -d ' ')

wildcards_clusterroles=$(kubectl get clusterroles -o json | jq '
.items[] | select(
.rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*")
)' | wc -l | tr -d ' ')

total=$((wildcards + wildcards_clusterroles))

if [ "${total}" -gt 0 ]; then
echo "Result: wildcards_present"
echo " Remaining Roles with wildcards : ${wildcards}"
echo " Remaining ClusterRoles with wildcards: ${wildcards_clusterroles}"
echo "Inspect remaining offenders with:"
echo " kubectl get roles --all-namespaces -o json | jq -r '.items[] | select(.rules[]? | (.verbs[]? == \"*\" or .resources[]? == \"*\" or .apiGroups[]? == \"*\")) | \"Role \\(.metadata.namespace)/\\(.metadata.name)\"'"
echo " kubectl get clusterroles -o json | jq -r '.items[] | select(.rules[]? | (.verbs[]? == \"*\" or .resources[]? == \"*\" or .apiGroups[]? == \"*\")) | \"ClusterRole \\(.metadata.name)\"'"
exit 1
else
echo "Result: no RBAC wildcards detected in Roles or ClusterRoles."
fi