Skip to main content

pods/exec Should Not Be Granted To Broad Subjects

More Info:

Advisory: review Roles/ClusterRoles that grant create on pods/exec. Exec into a running pod bypasses image immutability and admission controls.

Risk Level

High

Address

Security

Compliance Standards

  • Cloudanix Best Practice

Triage and Remediation

Remediation

Manual Steps
  1. List all Roles/ClusterRoles that grant create on pods/exec.

    • Run on: any machine with kubectl access
    kubectl get clusterroles -o json | jq -r '
    .items[]
    | select(.rules[]?
    | (.resources[]? == "pods/exec")
    and (.verbs[]? == "create")
    )
    | .metadata.name
    '
    kubectl get roles -A -o json | jq -r '
    .items[]
    | select(.rules[]?
    | (.resources[]? == "pods/exec")
    and (.verbs[]? == "create")
    )
    | (.metadata.namespace + ":" + .metadata.name)
    '
  2. Identify which subjects are bound to those Roles/ClusterRoles.

    • Run on: any machine with kubectl access
    # For each ClusterRole name found above, substitute into:
    kubectl get clusterrolebindings -o yaml | \
    yq 'select(.roleRef.kind == "ClusterRole" and .roleRef.name == "NAME_HERE") | {metadata:{name}, roleRef, subjects}'

    # For each namespaced Role "NAMESPACE:NAME" found above:
    kubectl get rolebindings -n NAMESPACE -o yaml | \
    yq 'select(.roleRef.kind == "Role" and .roleRef.name == "NAME_HERE") | {metadata:{name,namespace}, roleRef, subjects}'
  3. Evaluate whether subjects are “broad” and if they truly need exec.

    • Treat these as broad and usually inappropriate for pods/exec:
      • system:authenticated, system:unauthenticated, system:serviceaccounts, system:serviceaccounts:*
      • Any group that represents all developers or all users
      • Wildcard subjects like many service accounts across multiple namespaces
    • Keep create on pods/exec only for:
      • A small, named set of human users or groups (e.g. on-call SRE group)
      • Very limited support automation, with strong justification
  4. Design the least-privilege change.
    For each over‑broad binding you found:

    • Decide whether to:
      • Remove the binding entirely, or
      • Replace broad groups with specific human users/groups, or
      • Move pods/exec into a separate, more restricted Role/ClusterRole and bind only to a small operator group.
        Document which Role/ClusterRole and which RoleBinding/ClusterRoleBinding you will edit and the new subject list.
  5. Apply the RBAC changes via manifests or kubectl edit.

    • Preferred (GitOps-friendly): export, edit, and apply.
    • Run on: any machine with kubectl access
    # Example: export an existing ClusterRoleBinding for editing
    kubectl get clusterrolebinding NAME_HERE -o yaml > crb-updated.yaml
    # Edit subjects: remove broad groups, keep only named human operators
    vi crb-updated.yaml
    # Apply the change
    kubectl apply -f crb-updated.yaml

    # Similarly for RoleBinding:
    kubectl get rolebinding NAME_HERE -n NAMESPACE -o yaml > rb-updated.yaml
    vi rb-updated.yaml
    kubectl apply -f rb-updated.yaml

    If manifests are managed by IaC, make equivalent edits in the source repo instead of using kubectl edit, then redeploy.

  6. Verify that pods/exec is only granted to the intended narrow subjects.

    • Run on: any machine with kubectl access
    # Re-run evidence gathering
    kubectl get clusterroles -o json | jq -r '
    .items[]
    | select(.rules[]?
    | (.resources[]? == "pods/exec")
    and (.verbs[]? == "create")
    )
    | .metadata.name
    '
    kubectl get roles -N -o json | jq -r '
    .items[]
    | select(.rules[]?
    | (.resources[]? == "pods/exec")
    and (.verbs[]? == "create")
    )
    | (.metadata.namespace + ":" + .metadata.name)
    '

    # For each Role/ClusterRole listed, confirm subjects are only the small, named operator set:
    kubectl get clusterrolebindings -o yaml | yq 'select(.roleRef.name == "NAME_HERE") | {metadata:{name}, roleRef, subjects}'
    kubectl get rolebindings -n NAMESPACE -o yaml | yq 'select(.roleRef.name == "NAME_HERE") | {metadata:{name,namespace}, roleRef, subjects}'
Using kubectl
# 1) List all Roles/ClusterRoles that mention pods/exec
# Run on: any machine with kubectl access

kubectl get clusterroles -o yaml | grep -nE "pods/exec|pods\/exec" -A5 -B5
kubectl get roles --all-namespaces -o yaml | grep -nE "pods/exec|pods\/exec" -A5 -B5

What to look for:

  • Any rule with:
    • resources: ["pods/exec"] (or included in a list)
    • AND verbs including create (or *)

Example problematic rule snippet:

rules:
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["get", "list", "watch", "create"]

or:

rules:
- apiGroups: [""]
resources: ["pods", "pods/exec", "pods/log"]
verbs: ["*"]

# 2) Show full definitions for candidate ClusterRoles/Roles
# Replace <name> / <namespace> with names you saw above

# ClusterRoles
kubectl get clusterrole <name> -o yaml

# Namespaced Roles
kubectl get role <name> -n <namespace> -o yaml

What to look for:

  • Under rules::
    • resources includes pods/exec (explicitly or as part of a broader pattern like ["*"]).
    • verbs includes create or *.

Flag as higher-risk if:

  • The role is clearly generic or broad, e.g. cluster-admin, edit, view customizations, or any “developer”, “default”, or “*” style role used by many users/groups/service accounts.

# 3) Identify *who* gets each risky Role/ClusterRole (RoleBindings and ClusterRoleBindings)
# Run on: any machine with kubectl access

# List all bindings so you can correlate names
kubectl get clusterrolebindings -o yaml > /tmp/clusterrolebindings.yaml
kubectl get rolebindings --all-namespaces -o yaml > /tmp/rolebindings.yaml

Then search for each Role/ClusterRole name that can create on pods/exec:

# Example: find all bindings for ClusterRole 'dev-ops'
grep -n "name: dev-ops" -A4 -B4 /tmp/clusterrolebindings.yaml
grep -n "name: dev-ops" -A4 -B4 /tmp/rolebindings.yaml

What indicates a problem:

  • Bindings that attach a pods/exec create-capable role to broad subjects, such as:
    • system:authenticated
    • Large identity groups (e.g., developers, all-users, ci-users) used by many people
    • Generic service accounts used cluster-wide (e.g., default in many namespaces)
  • Bindings in many namespaces for the same permissive Role.

Example concerning binding snippet:

subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: dev-ops # this ClusterRole can create on pods/exec
apiGroup: rbac.authorization.k8s.io

# 4) Focus on the default high-privilege roles for context (read-only)
kubectl get clusterrole admin -o yaml
kubectl get clusterrole edit -o yaml
kubectl get clusterrole cluster-admin -o yaml

What to note:

  • These built-in roles are intentionally broad; if they (or custom equivalents) are widely granted, then many subjects may indirectly get pods/exec create.
  • This does NOT by itself say “fix this” — it tells you where exec is inherited from.

# 5) Optional: summarize all roles that include pods/exec with create
# (uses jsonpath and jq; jq must be installed)
kubectl get clusterroles -o json \
| jq '.items[]
| {name: .metadata.name,
rules: (.rules // [])
| map(select((.resources // []) | index("pods/exec"))
| select(((.verbs // []) | index("create")) or ((.verbs // []) | index("*"))))
}
| select(.rules | length > 0)'

Interpretation:

  • Every object printed is a ClusterRole that grants create (or *) on pods/exec.
  • For each of these, you must manually review:
    • Is this meant only for a very small, named set of human operators?
    • Are its bindings limited to those identities?
  • If the same role shows up here and is bound to broad groups or many service accounts (as seen in step 3), that’s a likely policy concern that needs design and approval rather than an automatic change.
Automation
#!/usr/bin/env bash
# Report Roles/ClusterRoles that grant create on pods/exec, and who can use them.
# Run on: any machine with kubectl access and current context set to target cluster.

set -euo pipefail

echo "=== Searching for Roles and ClusterRoles that grant create on pods/exec ==="
echo

echo "--- ClusterRoles ---"
kubectl get clusterroles -o json \
| jq -r '
.items[]
| select(
(.rules // [])
| map(
(.resources // [] | index("pods/exec"))
and
(.verbs // [] | index("create"))
)
| any
)
| .metadata.name
' | sort -u | tee /tmp/pods-exec-clusterroles.txt

echo
echo "--- Namespaced Roles ---"
kubectl get roles -A -o json \
| jq -r '
.items[]
| select(
(.rules // [])
| map(
(.resources // [] | index("pods/exec"))
and
(.verbs // [] | index("create"))
)
| any
)
| [.metadata.namespace, .metadata.name] | @tsv
' | sort -u | tee /tmp/pods-exec-roles.txt

echo
echo "=== Finding Subjects bound to these Roles/ClusterRoles ==="
echo

echo "--- ClusterRoleBindings using affected ClusterRoles ---"
if [ -s /tmp/pods-exec-clusterroles.txt ]; then
cr_list=$(paste -sd, /tmp/pods-exec-clusterroles.txt)
kubectl get clusterrolebindings -o json \
| jq -r --argjson crs '("'"$cr_list"'")|split(",")' '
.items[]
| select(.roleRef.kind=="ClusterRole" and (.roleRef.name as $n | $crs | index($n)))
| {
name: .metadata.name,
roleRef: .roleRef.name,
subjects: (.subjects // [])
}
| "ClusterRoleBinding: \(.name)\n Role: \(.roleRef)\n Subjects:\n" +
( .subjects[]
| " - kind=\(.kind) name=\(.name) namespace=\(.namespace // "-")"
)
'
else
echo "No ClusterRoles grant create on pods/exec."
fi

echo
echo "--- RoleBindings using affected Roles (namespaced) ---"
if [ -s /tmp/pods-exec-roles.txt ]; then
# Build a set of "namespace|name" for quick checking in jq
mapfile -t role_lines < /tmp/pods-exec-roles.txt
roles_json=$(printf '%s\n' "${role_lines[@]}" | awk -F '\t' '{print "{\"ns\":\""$1"\",\"name\":\""$2"\"}"}' | jq -s '.')
kubectl get rolebindings -A -o json \
| jq -r --argjson roles "$roles_json" '
.items[]
| select(
.roleRef.kind=="Role" and
([$.metadata.namespace, .roleRef.name] as $pair
| $roles | map(select(.ns==$pair[0] and .name==$pair[1])) | length > 0)
)
| {
namespace: .metadata.namespace,
name: .metadata.name,
roleRef: .roleRef.name,
subjects: (.subjects // [])
}
| "RoleBinding: \(.namespace)/\(.name)\n Role: \(.roleRef)\n Subjects:\n" +
( .subjects[]
| " - kind=\(.kind) name=\(.name) namespace=\(.namespace // "-")"
)
'
else
echo "No namespaced Roles grant create on pods/exec."
fi

echo
echo "=== Interpretation Guidance ==="
cat <<'EOF'
Potential problems to review manually:

1) Very broad subjects:
- kind=Group name=system:authenticated
- kind=Group name=system:serviceaccounts
- kind=Group name=system:serviceaccounts:<namespace>
- Wildcard-like or catch-all groups (e.g. "developers", "everyone").

2) Non-human subjects:
- kind=ServiceAccount for application workloads.

3) Cluster-wide scope:
- ClusterRoles with pods/exec create bound cluster-wide when most users
only need exec in specific namespaces.

4) Excessive bindings:
- Same pods/exec-granting role bound to many users/groups/namespaces.

Remediation is manual: restrict pods/exec (create) to a small, named set of
trusted human operators, and use separate, tightly scoped Roles where needed.
EOF

What output indicates a problem

  • Any ClusterRole or Role listed at the top sections is a candidate for review.
  • In the bindings sections, pay special attention if:
    • Subjects include wide groups like system:authenticated, system:serviceaccounts, or generic org-wide groups.
    • Subjects are ServiceAccount objects for workloads instead of human users.
    • The same exec-granting role is bound in many namespaces or via ClusterRoleBindings.