Minimize Access To Create Pods
More Info:​
The ability to create pods can be abused to run arbitrary workloads and escalate privileges. Restrict create access on pod objects to trusted subjects only.
Risk Level​
High
Address​
Security
Compliance Standards​
- CIS EKS
Triage and Remediation​
- Remediation
Remediation​
Manual Steps
-
List all Roles/ClusterRoles that can create pods
- Run on: any machine with kubectl access
kubectl get roles,clusterroles -A -o json | jq '[.items[] |select(.rules[]? |(.resources[]? == "pods" and .verbs[]? == "create"))] | .[].metadata | {kind: .ownerReferences[0].kind, name: .name, namespace: .namespace}'Review each listed Role/ClusterRole to determine which subjects (users/groups/serviceaccounts) really need to create pods.
-
Inspect a specific ClusterRole’s permissions
- Run on: any machine with kubectl access
Replace CLUSTERROLE_NAME with the actual name:
kubectl get clusterrole CLUSTERROLE_NAME -o yamlIdentify rules where
resourcesincludespodsandverbsincludescreate. - Run on: any machine with kubectl access
-
Remove
createon pods from a ClusterRole that should not have it- Run on: any machine with kubectl access
Edit the ClusterRole:
kubectl edit clusterrole CLUSTERROLE_NAMEIn each
rulesentry that hasresources: ["pods", ...](or includespods), removecreatefrom theverbslist (keep other verbs likeget,list,watchif still required), then save and exit. - Run on: any machine with kubectl access
-
Adjust namespace-scoped Roles similarly
- Run on: any machine with kubectl access
For each namespace where Roles were identified:
kubectl get role ROLE_NAME -n NAMESPACE -o yamlkubectl edit role ROLE_NAME -n NAMESPACEIn
rules, removecreatefromverbswhereverresourcesincludespods. Save when done. - Run on: any machine with kubectl access
-
Re-verify there are no unintended subjects that still require pod creation
- Run on: any machine with kubectl access
For any remaining Roles/ClusterRoles that must retainpodscreateaccess (e.g., system components or trusted CI/CD), document the justification and ensure their RoleBindings/ClusterRoleBindings are scoped only to the required subjects:
kubectl get rolebinding,clusterrolebinding -A -o yaml | grep -E "name: CLUSTERROLE_NAME|subjects:"Tighten bindings as needed (edit or delete) so only trusted subjects retain this access.
- Run on: any machine with kubectl access
-
Verification
- Run on: any machine with kubectl access
access=$(kubectl get roles,clusterroles -N kube-system -A -o json | jq '[.items[] |select(.rules[]? |(.resources[]? == "pods" and .verbs[]? == "create"))] | length')if [ "$access" -gt 0 ]; thenecho "pods_create_access"elseecho "no_pods_create_access_or_only_approved_remaining"fiConfirm that any remaining
podscreatepermissions correspond only to explicitly approved, trusted Roles/ClusterRoles.
Using kubectl
On any machine with kubectl access:
- Identify Roles/ClusterRoles that can create pods
kubectl get roles,clusterroles -A -o json | jq -r '
.items[]
| select(
.rules[]? |
(.resources[]? == "pods" and .verbs[]? == "create")
)
| "\(.kind)/\(.metadata.name) [ns=\(.metadata.namespace // "cluster-scope")]"
'
- For each offending Role/ClusterRole, fetch its manifest
- Namespaced Role (replace NAMESPACE and ROLE_NAME):
kubectl get role ROLE_NAME -n NAMESPACE -o yaml > role-ROLE_NAME.yaml
- ClusterRole (replace CLUSTERROLE_NAME):
kubectl get clusterrole CLUSTERROLE_NAME -o yaml > clusterrole-CLUSTERROLE_NAME.yaml
- Edit the manifest(s) locally to remove
createfrom any rule that grants it onpods.
Example before:
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "create"]
Example after (remove create):
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
If a rule is only for create on pods, delete that rule block entirely.
- Apply the updated manifest(s)
kubectl apply -f role-ROLE_NAME.yaml
kubectl apply -f clusterrole-CLUSTERROLE_NAME.yaml
(Repeat for each edited file.)
- Re-run the verification
access=$(kubectl get roles,clusterroles -N -o json | jq '
[.items[] |
select(
.rules[]? |
(.resources[]? == "pods" and .verbs[]? == "create")
)
] | length')
if [ "$access" -gt 0 ]; then
echo "pods_create_access"
else
echo "no_pods_create_access"
fi
Automation
#!/usr/bin/env bash
set -euo pipefail
# Minimize access to create pods on Roles and ClusterRoles
# Runs on: any machine with kubectl access and current-context pointing to target cluster
# REQUIREMENTS:
# - kubectl installed and configured
# - jq installed
echo "[+] Discovering Roles/ClusterRoles with 'create' on pods..."
tmpdir="$(mktemp -d)"
trap 'rm -rf "${tmpdir}"' EXIT
# Get all roles and clusterroles in JSON
kubectl get roles,clusterroles -A -o json > "${tmpdir}/all-roles.json"
# Extract all affected Role/ClusterRole names and kinds
jq -r '
.items[]
| select(.rules[]? | (.resources[]? == "pods" and .verbs[]? == "create"))
| [.kind, .metadata.name, (.metadata.namespace // "")] | @tsv
' "${tmpdir}/all-roles.json" > "${tmpdir}/affected.tsv" || true
if [ ! -s "${tmpdir}/affected.tsv" ]; then
echo "[+] No Roles or ClusterRoles grant create on pods. Nothing to change."
else
echo "[+] The following objects grant create on pods:"
column -t "${tmpdir}/affected.tsv"
echo
echo "[!] This script will REMOVE the 'create' verb from any rule that includes 'pods'."
echo " It does NOT modify RoleBindings/ClusterRoleBindings, nor does it change"
echo " rules granting other verbs on pods."
echo
while IFS=$'\t' read -r kind name namespace; do
ns_arg=()
if [ -n "${namespace}" ] && [ "${kind}" = "Role" ]; then
ns_arg=(--namespace "${namespace}")
fi
echo "[+] Processing ${kind}/${name}${namespace:+ in namespace ${namespace}}"
# Fetch current YAML
kubectl get "${kind}" "${name}" "${ns_arg[@]}" -o yaml > "${tmpdir}/${kind}_${namespace}_${name}.yaml"
# Use yq if available for safer YAML manipulation; otherwise use jq+yaml round-trip through kubectl
if command -v yq >/dev/null 2>&1; then
# Remove 'create' verb from any rule on 'pods'
yq '
(.rules // [])
|= map(
if (.resources // [] | any(. == "pods")) and (.verbs // [] | any(. == "create")) then
.verbs |= map(select(. != "create"))
else
.
end
)
' "${tmpdir}/${kind}_${namespace}_${name}.yaml" > "${tmpdir}/${kind}_${namespace}_${name}.patched.yaml"
else
# Fallback: convert to JSON, edit with jq, apply as YAML via kubectl.
# This will preserve semantics but may reorder fields.
kubectl get "${kind}" "${name}" "${ns_arg[@]}" -o json \
| jq '
.rules |=
( . // [] | map(
if (.resources // [] | any(. == "pods")) and (.verbs // [] | any(. == "create")) then
.verbs |= map(select(. != "create"))
else
.
end
)
)
' > "${tmpdir}/${kind}_${namespace}_${name}.patched.json"
# Convert back to YAML via kubectl apply -f - directly from JSON
fi
# Apply the patched object
if command -v yq >/dev/null 2>&1; then
kubectl apply -f "${tmpdir}/${kind}_${namespace}_${name}.patched.yaml"
else
kubectl apply -f "${tmpdir}/${kind}_${namespace}_${name}.patched.json"
fi
done < "${tmpdir}/affected.tsv"
fi
echo
echo "[+] Verifying that no Roles or ClusterRoles grant 'create' on pods..."
access=$(kubectl get roles,clusterroles -A -o json | jq '
[.items[] |
select(
.rules[]? |
(.resources[]? == "pods" and .verbs[]? == "create")
)
] | length')
if [ "${access}" -gt 0 ]; then
echo "[-] Verification FAILED: ${access} Role(s)/ClusterRole(s) still grant 'create' on pods."
echo " Inspect with:"
echo " kubectl get roles,clusterroles -A -o json | jq -r \"
.items[]
| select(.rules[]? | (.resources[]? == \\\"pods\\\" and .verbs[]? == \\\"create\\\"))
| {kind, name: .metadata.name, namespace: .metadata.namespace}
\""
exit 1
else
echo "[+] Verification PASSED: no Roles or ClusterRoles grant 'create' on pods."
fi