> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. On any machine with kubectl access, list all ClusterRoles and Roles that can create PersistentVolumes:
           ```bash theme={null}
           kubectl get clusterrole -o json | jq -r '
             .items[]
             | select(.rules[]? | any(.apiGroups[]? == "" and (.resources[]? | IN("persistentvolumes","persistentvolume")) and (.verbs[]? | IN("create","*"))))
             | .metadata.name' | sort -u
           ```
           ```bash theme={null}
           kubectl 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
           ```

        2. For each identified ClusterRole/Role, list the subjects bound to it to understand who can create PersistentVolumes:
           ```bash theme={null}
           # ClusterRoleBindings
           kubectl 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'
           ```
           ```bash theme={null}
           # 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'
           ```

        3. 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 describe` on ServiceAccounts):
           ```bash theme={null}
           kubectl describe clusterrole NAME_OF_CLUSTERROLE_HERE
           kubectl describe role -n NAMESPACE NAME_OF_ROLE_HERE
           kubectl describe sa -n NAMESPACE SERVICEACCOUNT_NAME
           ```

        4. For any non‑administrative subject that should not create PersistentVolumes, edit the associated Role/ClusterRole to remove the `create` verb on `persistentvolumes` while preserving other needed permissions:
           ```bash theme={null}
           kubectl edit clusterrole NAME_OF_CLUSTERROLE_HERE
           # or
           kubectl edit role -n NAMESPACE NAME_OF_ROLE_HERE
           ```
           In the editor, locate rules where `resources` includes `persistentvolumes` (or `persistentvolume`) and remove `create` (and `*` if overly broad) from `verbs` for that resource, or split the rule so PersistentVolumes no longer have `create` granted.

        5. 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):
           ```bash theme={null}
           # Remove bindings first
           kubectl delete clusterrolebinding NAME_OF_BINDING
           kubectl delete rolebinding -n NAMESPACE NAME_OF_BINDING

           # Optionally delete the unused role
           kubectl delete clusterrole NAME_OF_CLUSTERROLE_HERE
           kubectl delete role -n NAMESPACE NAME_OF_ROLE_HERE
           ```

        6. 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 `create` on PersistentVolumes:
           ```bash theme={null}
           kubectl get clusterrole -o json | jq -r '
             .items[]
             | select(.rules[]? | any(.apiGroups[]? == "" and (.resources[]? | IN("persistentvolumes","persistentvolume")) and (.verbs[]? | IN("create","*"))))
             | .metadata.name' | sort -u
           ```
           ```bash theme={null}
           kubectl 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
           ```
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 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 include `create` or `*` on `persistentvolumes`.

        ```bash theme={null}
        # 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"]` with `verbs:` containing `create` or `*`.
        * If the role name/purpose suggests non-admin usage, this is likely excessive.

        ```bash theme={null}
        # 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 `create` or `*` on `persistentvolumes` to non-admin roles is suspect.

        ```bash theme={null}
        # 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.
        * If such subjects are bound to a role that can create PersistentVolumes, this is a likely violation.

        ```bash theme={null}
        # 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 `create` on `persistentvolumes`, this needs review.

        ```bash theme={null}
        # 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 `yes` for identities that are not trusted administrators suggests privileges that should be reviewed and likely reduced.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/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:` or `ROLE:` entry listed by this script that includes:
          * `RESOURCES: persistentvolumes` (or includes `persistentvolumes` in a list), **and**
          * `VERBS:` containing `create`
        * These roles grant the ability to create `PersistentVolume` objects.
        * For each such role, review who receives it using the `clusterrolebinding` and `rolebinding` listings:
          * 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.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
