Minimize Access To Create Persistent Volumes
More Info:
Creating PersistentVolumes can enable privilege escalation via hostPath volumes. This permission should be limited to trusted administrators only.
Risk Level
High
Address
Security
Compliance Standards
- CIS AKS
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On any machine with kubectl access, list all ClusterRoles and Roles that can create PersistentVolumes:
kubectl get clusterrole -o json | jq -r '.items[]| select(.rules[]? | any(.apiGroups[]? == "" and (.resources[]? | IN("persistentvolumes","persistentvolume")) and (.verbs[]? | IN("create","*"))))| .metadata.name' | sort -ukubectl get role -A -o json | jq -r '.items[]| select(.rules[]? | any(.apiGroups[]? == "" and (.resources[]? | IN("persistentvolumes","persistentvolume")) and (.verbs[]? | IN("create","*"))))| [.metadata.namespace, .metadata.name] | @tsv' | sort -u -
For each identified ClusterRole/Role, list the subjects bound to it to understand who can create PersistentVolumes:
# ClusterRoleBindingskubectl get clusterrolebinding -o json | jq -r '.items[]| select(.roleRef.kind=="ClusterRole" and .roleRef.name==("NAME_OF_CLUSTERROLE_HERE"))| .metadata.name as $b| .subjects[]? | [$b,.kind,.namespace//"",.name] | @tsv'# RoleBindings (namespaced)kubectl get rolebinding -A -o json | jq -r '.items[]| select(.roleRef.kind=="Role" and .roleRef.name==("NAME_OF_ROLE_HERE"))| .metadata.namespace as $ns| .metadata.name as $b| .subjects[]? | [$ns,$b,.kind,.namespace//"",.name] | @tsv' -
For each Role/ClusterRole and its bound subjects, classify whether they are trusted administrators (e.g., dedicated admin groups, platform SRE service accounts) or non‑administrative users/workloads by reviewing your access model and group mappings (for example via your IdP or
kubectl describeon ServiceAccounts):kubectl describe clusterrole NAME_OF_CLUSTERROLE_HEREkubectl describe role -n NAMESPACE NAME_OF_ROLE_HEREkubectl describe sa -n NAMESPACE SERVICEACCOUNT_NAME -
For any non‑administrative subject that should not create PersistentVolumes, edit the associated Role/ClusterRole to remove the
createverb onpersistentvolumeswhile preserving other needed permissions:kubectl edit clusterrole NAME_OF_CLUSTERROLE_HERE# orkubectl edit role -n NAMESPACE NAME_OF_ROLE_HEREIn the editor, locate rules where
resourcesincludespersistentvolumes(orpersistentvolume) and removecreate(and*if overly broad) fromverbsfor that resource, or split the rule so PersistentVolumes no longer havecreategranted. -
Where a Role/ClusterRole exists solely to grant PV creation to non‑admins and is no longer required, remove the bindings or the role itself (after confirming no legitimate dependency):
# Remove bindings firstkubectl delete clusterrolebinding NAME_OF_BINDINGkubectl delete rolebinding -n NAMESPACE NAME_OF_BINDING# Optionally delete the unused rolekubectl delete clusterrole NAME_OF_CLUSTERROLE_HEREkubectl delete role -n NAMESPACE NAME_OF_ROLE_HERE -
Verify that PV creation is now limited to the intended admin roles only by re-running the evidence collection and confirming that only trusted admin roles retain
createon PersistentVolumes:kubectl get clusterrole -o json | jq -r '.items[]| select(.rules[]? | any(.apiGroups[]? == "" and (.resources[]? | IN("persistentvolumes","persistentvolume")) and (.verbs[]? | IN("create","*"))))| .metadata.name' | sort -ukubectl get role -A -o json | jq -r '.items[]| select(.rules[]? | any(.apiGroups[]? == "" and (.resources[]? | IN("persistentvolumes","persistentvolume")) and (.verbs[]? | IN("create","*"))))| [.metadata.namespace, .metadata.name] | @tsv' | sort -u
Using kubectl
# 1) List all ClusterRoles and Roles that can create PersistentVolumes
# Run on: any machine with kubectl access
kubectl get clusterrole -o json \
| jq -r '
.items[]
| select(
.rules[]
| (.resources[]? | select(. == "persistentvolumes"))
and
(.verbs[]? | select(. == "create" or . == "*"))
)
| .metadata.name
' | sort -u
kubectl get role --all-namespaces -o json \
| jq -r '
.items[]
| select(
.rules[]
| (.resources[]? | select(. == "persistentvolumes"))
and
(.verbs[]? | select(. == "create" or . == "*"))
)
| [.metadata.namespace, .metadata.name] | @tsv
' | sort -u
Problem indication:
- Any listed ClusterRole or Role is capable of creating PersistentVolumes.
- Pay particular attention to:
- Broadly named roles such as
edit,developer,default, or app-specific roles not intended for cluster administration. - Roles in application namespaces (e.g.,
team-a/*,prod-app/*) that includecreateor*onpersistentvolumes.
- Broadly named roles such as
# 2) Inspect the detailed rules of each suspicious ClusterRole
# Replace <clusterrole-name> with a name from the list above
kubectl get clusterrole <clusterrole-name> -o yaml
Problem indication:
- Under
rules:you see entries like:resources: ["persistentvolumes"]withverbs:containingcreateor*.
- If the role name/purpose suggests non-admin usage, this is likely excessive.
# 3) Inspect the detailed rules of each suspicious Role
# Replace <namespace> and <role-name> with values from the roles list
kubectl get role -n <namespace> <role-name> -o yaml
Problem indication:
- Same as for ClusterRole: any rule granting
createor*onpersistentvolumesto non-admin roles is suspect.
# 4) See who is bound to a suspicious ClusterRole
# Replace <clusterrole-name> with a candidate from step 1
kubectl get clusterrolebinding -o json \
| jq -r --arg CR "<clusterrole-name>" '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == $CR)
| .metadata.name
'
# For each binding:
kubectl get clusterrolebinding <binding-name> -o yaml
Problem indication:
subjects:includes:- Broad groups (e.g.,
system:authenticated,developers,ci-users). - Service accounts used by applications rather than administrators.
- Broad groups (e.g.,
- If such subjects are bound to a role that can create PersistentVolumes, this is a likely violation.
# 5) See who is bound to a suspicious Role
# Replace <namespace> and <role-name>
kubectl get rolebinding -n <namespace> -o json \
| jq -r --arg RB "<role-name>" '
.items[]
| select(.roleRef.kind == "Role" and .roleRef.name == $RB)
| .metadata.name
'
# For each binding:
kubectl get rolebinding -n <namespace> <binding-name> -o yaml
Problem indication:
- Same as for ClusterRoleBinding: if non-admin users, groups, or service accounts are subjects, and the role grants
createonpersistentvolumes, this needs review.
# 6) (Optional) Spot-check who can create PersistentVolumes (simulation)
# Replace <user-or-sa> with an identity you want to test, e.g.:
# --user alice
# --serviceaccount app-namespace:app-sa
kubectl auth can-i create persistentvolumes --user <user-or-sa>
Problem indication:
- Output
yesfor identities that are not trusted administrators suggests privileges that should be reviewed and likely reduced.
Automation
#!/usr/bin/env bash
# Report ClusterRoles/Roles that can create PersistentVolumes (pv) for review.
# Run on: any machine with kubectl access and current context set to the target cluster.
set -euo pipefail
echo "=== Checking ClusterRoles with create access on persistentvolumes ==="
kubectl get clusterrole -o json \
| jq -r '
.items[]
| {
name: .metadata.name,
rules: (
.rules[]
| select(.resources[]? == "persistentvolumes")
| select(.verbs[]? == "create")
)
}
| select(.rules != null)
| "CLUSTERROLE: \(.name)\n RESOURCES: \(.rules.resources | join(","))\n VERBS: \(.rules.verbs | join(","))\n"
' | sed '/^$/d' || true
echo
echo "=== Checking Roles with create access on persistentvolumes (all namespaces) ==="
kubectl get role -A -o json \
| jq -r '
.items[]
| {
namespace: .metadata.namespace,
name: .metadata.name,
rules: (
.rules[]
| select(.resources[]? == "persistentvolumes")
| select(.verbs[]? == "create")
)
}
| select(.rules != null)
| "ROLE: \(.namespace)/\(.name)\n RESOURCES: \(.rules.resources | join(","))\n VERBS: \(.rules.verbs | join(","))\n"
' | sed '/^$/d' || true
echo
echo "=== Showing RoleBindings/ClusterRoleBindings that reference the above (for context) ==="
echo "This helps identify which subjects (users/groups/serviceaccounts) receive PV create permissions."
echo
echo "--- ClusterRoleBindings (may grant PV create via ClusterRoles above) ---"
kubectl get clusterrolebinding -o wide
echo
echo "--- RoleBindings (may grant PV create via Roles above, per-namespace) ---"
kubectl get rolebinding -A -o wide
Explanation of output that indicates a problem:
- Any
CLUSTERROLE:orROLE:entry listed by this script that includes:RESOURCES: persistentvolumes(or includespersistentvolumesin a list), andVERBS:containingcreate
- These roles grant the ability to create
PersistentVolumeobjects. - For each such role, review who receives it using the
clusterrolebindingandrolebindinglistings:- If bound to non-administrative users, groups, or service accounts, this is a potential problem per CISAKS 4.1.8 and should be reviewed and likely removed or restricted.