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
Remediation​
Manual Steps
-
Identify the offending bindings and roles (any machine with kubectl access)
kubectl get rolebindings,clusterrolebindings --all-namespaces -o widekubectl get roles,clusterroles --all-namespaces -o yaml | grep -n "pods/exec" -n -
Inspect a flagged binding and its role (any machine with kubectl access)
Replace the names from the finding/audit output.# Inspect the binding that uses broad subjectskubectl get clusterrolebinding <BINDING_NAME> -o yaml# Inspect the referenced ClusterRole to confirm pods/exec create permissionskubectl get clusterrole <CLUSTERROLE_NAME> -o yaml -
Decide how pods/exec should be used (manual review)
- Determine which specific human users or a small set of named service accounts truly need
kubectl exec. - For any broad subject (
system:authenticated,system:unauthenticated,system:anonymous,system:serviceaccounts), decide whether:- exec should be removed entirely for that binding, or
- the binding should be replaced by a binding to specific users/groups/service accounts.
- Determine which specific human users or a small set of named service accounts truly need
-
Restrict or remove the broad binding (any machine with kubectl access)
Example: edit an existing ClusterRoleBinding to remove broad subjects and add specific ones.kubectl edit clusterrolebinding <BINDING_NAME>In your editor:
- Under
subjects:, delete entries wherenameis any of:
system:authenticated,system:unauthenticated,system:anonymous,system:serviceaccounts. - Optionally add tightly scoped subjects such as:
subjects:- kind: UserapiGroup: rbac.authorization.k8s.ioname: alice@example.com- kind: ServiceAccountname: exec-operatornamespace: ops
- Save and exit to apply.
- Under
-
Optionally narrow the ClusterRole itself (any machine with kubectl access)
If the ClusterRole grants more than needed (e.g.,"resources: ["*"]orverbs: ["*"]), edit it:kubectl edit clusterrole <CLUSTERROLE_NAME>In your editor, change the relevant rule to something like:
rules:- apiGroups: [""]resources: ["pods/exec"]verbs: ["create"]or remove
pods/execfromresourcesentirely if exec is no longer required. -
Verify the remediation (any machine with kubectl access)
{ kubectl get roles,clusterroles --all-namespaces -o jsonkubectl get rolebindings,clusterrolebindings --all-namespaces -o json} | jq -rs '.[0] as $roles | .[1] |def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];[ $roles.items[]| select(any(.rules[]?;(any(.resources[]?; . == "pods/exec" or . == "*"))and (any(.verbs[]?; . == "create" or . == "*"))))| { kind: .kind, ns: (.metadata.namespace // ""), name: .metadata.name } ] as $execRoles| [ .items[]| .kind as $kind | .metadata as $m | .roleRef as $ref| select(any($execRoles[];.name == $ref.name and .kind == $ref.kindand (.ns == "" or .ns == ($m.namespace // ""))))| ((.subjects // [])[] | select(.name as $n | broad | index($n)))| "kind=\($kind)"+ (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)+ " name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)+ " subject=\(.name) roleRef=\($ref.kind)/\($ref.name) grants=pods/exec is_compliant=false"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'Confirm the output is
is_compliant=trueand no bindings with broad subjects remain forpods/exec.
Using kubectl
On any machine with kubectl access:
- Identify the offending bindings and roles
kubectl get clusterrolebindings,rolebindings --all-namespaces -o wide
kubectl get clusterroles,roles --all-namespaces -o yaml | grep -E "name: pods/exec|\- \\\*" -n
Focus on bindings where roleRef points to a Role/ClusterRole that allows create on pods/exec and subjects include any of:
system:authenticatedsystem:unauthenticatedsystem:anonymoussystem:serviceaccounts
- Remove broad subjects from the binding
For each offending binding, edit it to remove the broad subject and (optionally) replace it with specific named users/groups.
ClusterRoleBinding example (cluster‑wide):
kubectl edit clusterrolebinding <binding-name>
In the editor, change e.g.:
subjects:
- kind: Group
name: system:authenticated
apiGroup: rbac.authorization.k8s.io
to something like:
subjects:
- kind: User
name: alice@example.com
apiGroup: rbac.authorization.k8s.io
- kind: User
name: bob@example.com
apiGroup: rbac.authorization.k8s.io
or remove the subjects entry entirely if no one should have pods/exec via this binding.
RoleBinding example (namespace‑scoped):
kubectl edit rolebinding <binding-name> -n <namespace>
Adjust the subjects section the same way: eliminate system:authenticated, system:unauthenticated, system:anonymous, or system:serviceaccounts, keeping only specific users or a single, named service account if absolutely required.
- (Optional) Split out a least‑privilege exec role
If a broad ClusterRole currently contains pods/exec among many other permissions, consider:
kubectl get clusterrole <broad-role-name> -o yaml > /tmp/exec-role.yaml
Edit /tmp/exec-role.yaml to:
- Change
metadata.nameto a new, restrictive name, e.g.exec-operators-only. - Remove all rules except the minimal exec rule, for example:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: exec-operators-only
rules:
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
Apply it:
kubectl apply -f /tmp/exec-role.yaml
Then create a dedicated binding for named human operators only:
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: exec-operators-only-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: exec-operators-only
subjects:
- kind: User
name: alice@example.com
apiGroup: rbac.authorization.k8s.io
- kind: User
name: bob@example.com
apiGroup: rbac.authorization.k8s.io
EOF
- Verification
Run the provided audit on any machine with kubectl access:
{ kubectl get roles,clusterroles --all-namespaces -o json
kubectl get rolebindings,clusterrolebindings --all-namespaces -o json
} | jq -rs '
.[0] as $roles | .[1] |
def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];
[ $roles.items[]
| select(any(.rules[]?;
(any(.resources[]?; . == "pods/exec" or . == "*"))
and (any(.verbs[]?; . == "create" or . == "*"))))
| { kind: .kind, ns: (.metadata.namespace // ""), name: .metadata.name } ] as $execRoles
| [ .items[]
| .kind as $kind | .metadata as $m | .roleRef as $ref
| select(any($execRoles[];
.name == $ref.name and .kind == $ref.kind
and (.ns == "" or .ns == ($m.namespace // ""))))
| ((.subjects // [])[] | select(.name as $n | broad | index($n)))
| "kind=\($kind)"
+ (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
+ " name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ " subject=\(.name) roleRef=\($ref.kind)/\($ref.name) grants=pods/exec is_compliant=false"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
#
# Restrict broad subjects from Roles/ClusterRoles that grant create on pods/exec.
# Platform: Amazon EKS
# Runs on: any machine with kubectl access and jq installed.
#
# Behavior:
# - Detects RoleBindings/ClusterRoleBindings whose role grants create on pods/exec
# and which are bound to any of:
# system:authenticated, system:unauthenticated, system:anonymous, system:serviceaccounts
# - Removes ONLY those broad subjects from the binding, leaving other subjects intact.
# - Safe to re-run: once subjects are removed, no further changes are made.
# - Prints a final compliance summary using the benchmark audit logic.
set -euo pipefail
# Ensure required tools
command -v kubectl >/dev/null 2>&1 || { echo "kubectl not found in PATH" >&2; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq not found in PATH" >&2; exit 1; }
BROAD_SUBJECTS='["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"]'
echo "Discovering Roles and ClusterRoles that grant create on pods/exec..."
# Build JSON with exec-capable roles/clusterroles and all bindings
COMBINED_JSON="$( { kubectl get roles,clusterroles --all-namespaces -o json ; kubectl get rolebindings,clusterrolebindings --all-namespaces -o json ; } )"
# Extract exec-capable roles/clusterroles
EXEC_ROLES_JSON="$(printf '%s\n' "$COMBINED_JSON" | jq -rs '
.[0].items[]
| select(any(.rules[]?;
(any(.resources[]?; . == "pods/exec" or . == "*"))
and (any(.verbs[]?; . == "create" or . == "*"))))
| { kind: .kind,
ns: (.metadata.namespace // ""),
name: .metadata.name }')"
if [ -z "$EXEC_ROLES_JSON" ] || [ "$EXEC_ROLES_JSON" = "null" ]; then
echo "No Roles/ClusterRoles grant create on pods/exec; nothing to change."
else
echo "Identifying RoleBindings/ClusterRoleBindings with broad subjects bound to exec-capable roles..."
BINDINGS_JSON="$(printf '%s\n' "$COMBINED_JSON" | jq -rs --argjson execRoles "$EXEC_ROLES_JSON" --argjson broad "$BROAD_SUBJECTS" '
.[1].items
| map(
. as $b
| .kind as $kind
| .metadata as $m
| .roleRef as $ref
| select(any($execRoles[];
.name == $ref.name and .kind == $ref.kind
and (.ns == "" or .ns == ($m.namespace // ""))))
| .subjects // [] as $subjects
| $subjects
| map(select(.name as $n | $broad | index($n))) as $broadSubjects
| select($broadSubjects | length > 0)
| {
kind: $kind,
namespace: ($m.namespace // ""),
name: $m.name,
apiVersion: "rbac.authorization.k8s.io/v1",
uid: $m.uid,
roleRefKind: $ref.kind,
roleRefName: $ref.name,
broadSubjects: $broadSubjects
}
)
')"
if [ -z "$BINDINGS_JSON" ] || [ "$BINDINGS_JSON" = "null" ] || [ "$(printf '%s' "$BINDINGS_JSON" | jq 'length')" -eq 0 ]; then
echo "No RoleBindings/ClusterRoleBindings bind broad subjects to exec-capable roles; nothing to change."
else
echo "The following bindings will have broad subjects removed:"
printf '%s\n' "$BINDINGS_JSON" | jq -r '
.[]
| "kind=\(.kind)"
+ (if .namespace == "" then "" else " ns=\(.namespace)" end)
+ " name=\(.name) roleRef=\(.roleRefKind)/\(.roleRefName)"
+ " removing_subjects=\([.broadSubjects[].name] | join(","))"
'
# Iterate and patch each binding
MAPFILE=()
while IFS= read -r line; do
MAPFILE+=("$line")
done < <(printf '%s\n' "$BINDINGS_JSON" | jq -c '.[]')
for item in "${MAPFILE[@]}"; do
kind=$(printf '%s\n' "$item" | jq -r '.kind')
ns=$(printf '%s\n' "$item" | jq -r '.namespace')
name=$(printf '%s\n' "$item" | jq -r '.name')
# Build a subjects list excluding broad subjects
patch_subjects=$(kubectl get "$kind" "$name" ${ns:+-n "$ns"} -o json \
| jq --argjson broad "$BROAD_SUBJECTS" '
.subjects // []
| map(select(.name as $n | $broad | index($n) | not))
')
# Construct full patch; if resulting subjects list is empty, set subjects: []
patch_json=$(jq -n --argjson subs "$patch_subjects" '{ "subjects": $subs }')
echo "Patching $kind ${ns:+namespace=$ns }name=$name to remove broad subjects..."
kubectl patch "$kind" "$name" ${ns:+-n "$ns"} --type=merge -p "$patch_json" 1>/dev/null
done
fi
fi
echo "Re-running compliance check..."
# Verification: run the benchmark audit logic
{ kubectl get roles,clusterroles --all-namespaces -o json
kubectl get rolebindings,clusterrolebindings --all-namespaces -o json
} | jq -rs '
.[0] as $roles | .[1] |
def broad: ["system:authenticated","system:unauthenticated","system:anonymous","system:serviceaccounts"];
[ $roles.items[]
| select(any(.rules[]?;
(any(.resources[]?; . == "pods/exec" or . == "*"))
and (any(.verbs[]?; . == "create" or . == "*"))))
| { kind: .kind, ns: (.metadata.namespace // ""), name: .metadata.name } ] as $execRoles
| [ .items[]
| .kind as $kind | .metadata as $m | .roleRef as $ref
| select(any($execRoles[];
.name == $ref.name and .kind == $ref.kind
and (.ns == "" or .ns == ($m.namespace // ""))))
| ((.subjects // [])[] | select(.name as $n | broad | index($n)))
| "kind=\($kind)"
+ (if ($m.namespace // "") == "" then "" else " ns=\($m.namespace)" end)
+ " name=\($m.name) uid=\($m.uid) apiVersion=rbac.authorization.k8s.io/v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ " subject=\(.name) roleRef=\($ref.kind)/\($ref.name) grants=pods/exec is_compliant=false"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'