Minimize Access To Create Pods
More Info:
The ability to create Pods can be abused to run privileged workloads and escalate access. Limit pod-create rights to the minimum required.
Risk Level
High
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List who can create pods and through which roles
- Run on: any machine with kubectl access
kubectl get clusterrole,role -A -o yaml | grep -nE '^(kind: (ClusterRole|Role)| name:| apiGroups:| resources:| verbs:)'Then more precisely:
kubectl get clusterrole,role -A -o yaml \| awk '/^kind: /{k=$2} /^ name: /{n=$2} /resources:/{r=""} /- pods/{r="pods"} /verbs:/{if(r=="pods"){print k,n;}}' \| sort -u -
Inspect a specific role/clusterrole that grants pod create
- Run on: any machine with kubectl access
Replace<KIND>withroleorclusterrole,<NAME>with the name from step 1, and<NAMESPACE>for Roles (omit for ClusterRoles):
# For a Rolekubectl get role <NAME> -n <NAMESPACE> -o yaml# For a ClusterRolekubectl get clusterrole <NAME> -o yaml - Run on: any machine with kubectl access
-
Decide whether each “create pods” permission is truly required (manual review)
- For each role/clusterrole from step 1:
- Identify what workload or team uses it by checking its rolebindings:
kubectl get rolebinding -A --field-selector=roleRef.kind=Role,roleRef.name=<NAME>kubectl get clusterrolebinding -A --field-selector=roleRef.kind=ClusterRole,roleRef.name=<NAME>
- Confirm whether those subjects actually need to create pods. If not clearly required (for example, they only need to read or list pods), plan to remove the
createverb forpodsfrom that role.
- Identify what workload or team uses it by checking its rolebindings:
- For each role/clusterrole from step 1:
-
Edit the role/clusterrole to remove pod create rights
- Run on: any machine with kubectl access
- For each role/clusterrole where
createonpodsis not strictly needed:
# Edit a Rolekubectl edit role <NAME> -n <NAMESPACE># Edit a ClusterRolekubectl edit clusterrole <NAME>In the editor, locate any
rulesentry whereresourcesincludespodsandverbsincludescreate, and removecreatefrom that list (or remove the whole rule if it only existed for pod creation). Save and exit. -
If authorization is managed via manifests/IaC, update the source files
- Run on: any machine with access to your Git/IaC repo
- Locate the YAML defining the same
Role/ClusterRoleobjects (matching names from step 4) and removecreatefromverbsforpodsthere as well, then apply:
kubectl apply -f <path-to-updated-rbac-manifests>.yaml -
Verify that unauthenticated users cannot create pods cluster-wide
- Run on: any machine with kubectl access
echo "canCreatePodsAsSystemAuthenticated: $(kubectl auth can-i create pods --all-namespaces --as=system:authenticated)"Ensure the output is:
canCreatePodsAsSystemAuthenticated: no
Using kubectl
# 1) List all Roles/ClusterRoles that can create pods
# Run on: any machine with kubectl access
kubectl get clusterrole -o json | jq -r '
.items[]
| select(.rules[]? | any(.verbs[]?; . == "create") and any(.resources[]?; . == "pods"))
| .metadata.name
' | sort -u
kubectl get role -A -o json | jq -r '
.items[]
| select(.rules[]? | any(.verbs[]?; . == "create") and any(.resources[]?; . == "pods"))
| "\(.metadata.namespace):\(.metadata.name)"
' | sort -u
Review the roles/clusterroles above and decide which truly need to create pods. For each one that should not have this right, remove create from the pods rule.
Example: editing an over‑permissive ClusterRole named dev-users:
# 2) Edit the ClusterRole to remove "create" on pods
# Run on: any machine with kubectl access
kubectl get clusterrole dev-users -o yaml > dev-users-clusterrole.yaml
Open dev-users-clusterrole.yaml in an editor and, in any rule with resources: ["pods"] (or including pods), remove create from verbs:
# BEFORE
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch","create","delete"]
# AFTER (create removed)
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch","delete"]
Then apply the change:
kubectl apply -f dev-users-clusterrole.yaml
Example: editing a namespaced Role dev-namespace:dev-role:
# 3) Edit the Role to remove "create" on pods
# Run on: any machine with kubectl access
kubectl get role dev-role -n dev-namespace -o yaml > dev-role.yaml
Edit dev-role.yaml similarly, removing create from any rule that lists pods in resources, then:
kubectl apply -f dev-role.yaml
Repeat this edit/apply process for each Role/ClusterRole that should not be able to create pods.
# 4) Verification: confirm that generic system:authenticated users cannot create pods
# Run on: any machine with kubectl access
kubectl auth can-i create pods --all-namespaces --as=system:authenticated
Automation
#!/usr/bin/env bash
#
# Minimize Access To Create Pods (CIS Kubernetes 5.1.4)
#
# Scope: any machine with kubectl access and credentials
#
# Behavior:
# - Enumerates all Roles and ClusterRoles that allow "create" on "pods"
# - Prints them for review
# - For each such role, interactively asks whether to remove that permission
# - If approved, patches the role rules to drop "create" on "pods" only
# - Skips system roles (kube-system, system:*)
# - Verifies using `kubectl auth can-i` at the end
#
# NOTE:
# This control is MANUAL. There is no one-size-fits-all automated fix.
# This script safely assists review and editing but always requires
# an explicit per-role decision.
set -euo pipefail
# --------- Prerequisites ---------
command -v kubectl >/dev/null 2>&1 || {
echo "kubectl not found in PATH." >&2
exit 1
}
echo "Using kubectl context:"
kubectl config current-context || {
echo "No current kubectl context; configure access and re-run." >&2
exit 1
}
echo
# --------- Helper: confirm ---------
confirm() {
# $1 = prompt
while true; do
read -r -p "$1 [y/N]: " ans
case "$ans" in
[Yy]*) return 0 ;;
[Nn]*|"") return 1 ;;
esac
done
}
# --------- Helper: patch a Role/ClusterRole to drop 'create' on 'pods' ---------
patch_role_drop_create_pods() {
local kind="$1" # Role or ClusterRole
local name="$2"
local namespace="${3:-}"
# Build kubectl get command
local get_cmd=(kubectl get "$kind" "$name" -o json)
if [[ "$kind" == "Role" ]]; then
get_cmd=(kubectl get role "$name" -n "$namespace" -o json)
fi
# Use jq to remove 'create' from verbs where resources include 'pods'
# and drop any rule that ends up with empty verbs/resources/apiGroups
local tmpfile
tmpfile="$(mktemp)"
"${get_cmd[@]}" \
| jq '
.rules |=
(
map(
if ( .resources? // [] ) | index("pods") != null then
# remove "create" from verbs
.verbs |= map(select(. != "create"))
else
.
end
)
# drop rules that have no verbs or no resources
| map(select(
(.verbs // []) | length > 0
and
(.resources // []) | length > 0
))
)
' > "$tmpfile"
# If resulting rules field is unchanged (no pod-create to remove), skip
if diff -q <("${get_cmd[@]}") "$tmpfile" >/dev/null 2>&1; then
echo " No change needed (no 'create' on 'pods' found or already removed)."
rm -f "$tmpfile"
return 0
fi
echo " Applying patch..."
if [[ "$kind" == "Role" ]]; then
kubectl apply -f "$tmpfile"
else
kubectl apply -f "$tmpfile"
fi
rm -f "$tmpfile"
echo " Patch applied."
}
# --------- Enumerate roles with create pods ---------
echo "Discovering Roles with 'create' verb on 'pods'..."
mapfile -t roles_with_create_pods < <(
kubectl get roles --all-namespaces -o json \
| jq -r '
.items[]
| select(
any(.rules[]?; (.resources? // []) | index("pods") != null
and (.verbs? // []) | index("create") != null)
)
| "\(.metadata.namespace) \(.metadata.name)"
'
)
echo "Discovering ClusterRoles with 'create' verb on 'pods'..."
mapfile -t clusterroles_with_create_pods < <(
kubectl get clusterroles -o json \
| jq -r '
.items[]
| select(
any(.rules[]?; (.resources? // []) | index("pods") != null
and (.verbs? // []) | index("create") != null)
)
| .metadata.name
'
)
echo
echo "Roles with 'create' on 'pods':"
if ((${#roles_with_create_pods[@]} == 0)); then
echo " (none)"
else
printf ' %s\n' "${roles_with_create_pods[@]}"
fi
echo
echo "ClusterRoles with 'create' on 'pods':"
if ((${#clusterroles_with_create_pods[@]} == 0)); then
echo " (none)"
else
printf ' %s\n' "${clusterroles_with_create_pods[@]}"
fi
echo
# --------- Interactive remediation ---------
echo "Review each role and decide whether to remove 'create' on 'pods'."
echo "System and default roles are skipped automatically."
echo
# Process namespace Roles
for line in "${roles_with_create_pods[@]}"; do
ns="${line%% *}"
rname="${line#* }"
# Skip kube-system and system-critical namespaces by default
if [[ "$ns" == "kube-system" || "$ns" == "kube-public" || "$ns" == "kube-node-lease" ]]; then
echo "Skipping Role '$rname' in namespace '$ns' (system namespace)."
continue
fi
echo
echo "Role: $rname"
echo "Namespace: $ns"
echo "Current rules (filtered to pods/create):"
kubectl get role "$rname" -n "$ns" -o json \
| jq '
.rules[]
| select((.resources? // []) | index("pods") != null
and (.verbs? // []) | index("create") != null)
'
if confirm "Remove 'create' on 'pods' from this Role?"; then
patch_role_drop_create_pods "Role" "$rname" "$ns"
else
echo " Left unchanged."
fi
done
# Process ClusterRoles
for cr in "${clusterroles_with_create_pods[@]}"; do
# Skip system clusterroles
if [[ "$cr" == system:* || "$cr" == "cluster-admin" || "$cr" == "admin" || "$cr" == "edit" ]]; then
echo "Skipping ClusterRole '$cr' (commonly system/privileged role)."
continue
fi
echo
echo "ClusterRole: $cr"
echo "Current rules (filtered to pods/create):"
kubectl get clusterrole "$cr" -o json \
| jq '
.rules[]
| select((.resources? // []) | index("pods") != null
and (.verbs? // []) | index("create") != null)
'
if confirm "Remove 'create' on 'pods' from this ClusterRole?"; then
patch_role_drop_create_pods "ClusterRole" "$cr"
else
echo " Left unchanged."
fi
done
# --------- Final verification ---------
echo
echo "Final verification (CIS 5.1.4):"
echo "canCreatePodsAsSystemAuthenticated: $(kubectl auth can-i create pods --all-namespaces --as=system:authenticated)"
echo
echo "Review the above result: for strict minimization, this should generally be 'no'."