> ## 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:

The ability to create PersistentVolumes can be used to mount hostPath volumes and access the underlying node. Limit who can create them.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* CIS Kubernetes

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify all subjects that can create PersistentVolumes**
           * Run on: any machine with kubectl access
           ```bash theme={null}
           kubectl get clusterrole -o json | jq -r '
             .items[]
             | select(.rules[]? 
                      | any(.apiGroups[]? == "" or .apiGroups[]? == "v1";
                            .resources[]? == "persistentvolumes";
                            (.verbs[]? == "create" or .verbs[]? == "*")))
             | .metadata.name' | sort -u
           ```
           Save this list of ClusterRoles for review.

        2. **Review the permissions in each identified ClusterRole**
           * Run on: any machine with kubectl access
           ```bash theme={null}
           for cr in $(kubectl get clusterrole -o name); do
             kubectl get "$cr" -o yaml | \
             yq '. | select(.rules[]? 
                | any(.apiGroups[]? == "" or .apiGroups[]? == "v1";
                      .resources[]? == "persistentvolumes";
                      (.verbs[]? == "create" or .verbs[]? == "*")) )'
           done
           ```
           For each displayed ClusterRole, decide if the `create` permission on `persistentvolumes` is truly required (e.g., storage admin vs general workloads).

        3. **Map ClusterRoles with PV create permission to actual users/groups/service accounts**
           * Run on: any machine with kubectl access
           ```bash theme={null}
           # ClusterRoleBindings
           kubectl get clusterrolebinding -o yaml | \
           yq '.items[]
              | select(.roleRef.kind == "ClusterRole" and
                       (.roleRef.name == "cluster-admin" or
                        (.roleRef.name as $r |
                         env.CR_LIST | split(" ") | any(. == $r))))
              | {name: .metadata.name, role: .roleRef.name, subjects: .subjects}' \
              CR_LIST="$(kubectl get clusterrole -o json | jq -r '
                .items[]
                | select(.rules[]? 
                         | any(.apiGroups[]? == "" or .apiGroups[]? == "v1";
                               .resources[]? == "persistentvolumes";
                               (.verbs[]? == "create" or .verbs[]? == "*")))
                | .metadata.name' | tr '\n' ' ')"
           ```
           Manually review each binding’s `subjects` to see which identities gain PV create capability.

        4. **Decide and implement least-privilege changes**\
           For each ClusterRole where `create` on `persistentvolumes` is NOT strictly needed:
           * Edit the ClusterRole to remove `create` (and `*` if unused) for `persistentvolumes`:
             * Run on: any machine with kubectl access
             ```bash theme={null}
             kubectl edit clusterrole <CLUSTERROLE_NAME>
             ```
             In the `rules` section, remove `create` from `verbs` for `resources: ["persistentvolumes"]`, or split out a dedicated rule if other verbs are still required.\
             If an identity needs only PV consumption, bind it instead to a narrower role that lacks PV `create`.

        5. **If PV creation is needed, confine it to dedicated admin roles**
           * Ensure only a small, storage-admin–style ClusterRole retains `create` on `persistentvolumes`.
           * Rebind ClusterRoleBindings so that:
             * General developers and application service accounts do NOT reference that ClusterRole.
             * Only designated admin groups/users (e.g., an ops group in your IdP) are subjects of the storage admin ClusterRoleBinding:
               ```bash theme={null}
               kubectl edit clusterrolebinding <BINDING_NAME>
               ```
               Adjust the `subjects` list accordingly.

        6. **Verify effective reduction of PV create access**
           * Re-run the permission discovery and confirm only the intended ClusterRoles retain PV `create`:
             ```bash theme={null}
             kubectl get clusterrole -o json | jq -r '
               .items[]
               | select(.rules[]? 
                        | any(.apiGroups[]? == "" or .apiGroups[]? == "v1";
                              .resources[]? == "persistentvolumes";
                              (.verbs[]? == "create" or .verbs[]? == "*")))
               | .metadata.name' | sort -u
             ```
           * Optionally, as a non-privileged user/service account, attempt to create a `PersistentVolume` and confirm it is rejected with a `forbidden` error.
      </Accordion>

      <Accordion title="Using kubectl">
        ### Using kubectl

        **1. List all ClusterRoles that can create PersistentVolumes**

        Run on: any machine with `kubectl` access.

        ```sh theme={null}
        kubectl get clusterroles -o json | jq -r '
          .items[]
          | select(
              (.rules // [])
              | map(
                  (.resources // []) as $r
                  | (.verbs // []) as $v
                  | select( ("persistentvolumes" | IN($r[]?)) and ("create" | IN($v[]?)) )
                )
              | length > 0
            )
          | .metadata.name
        '
        ```

        If you don’t have `jq`, use:

        ```sh theme={null}
        kubectl get clusterroles -o yaml | \
          awk '
            $0 ~ /^kind: ClusterRole/ { inrole=1; name="" }
            inrole && $1 == "name:" { name=$2 }
            /rules:/ { inrules=1 }
            inrules && /resources:/ { inres=1 }
            inrules && /verbs:/ { inverbs=1 }
            inres && /persistentvolumes/ { gotpv=1 }
            inverbs && /create/ { gotcreate=1 }
            /^$/ {
              if (inrole && gotpv && gotcreate) print name
              inrole=inrules=inres=inverbs=gotpv=gotcreate=0
            }
          '
        ```

        **Output indicating a problem:**\
        Any ClusterRole name returned here grants `create` on `persistentvolumes` to whoever is bound to it. These roles need review to see if that permission is justified.

        ***

        **2. Inspect each identified ClusterRole’s rules in detail**

        For each ClusterRole name from step 1:

        ```sh theme={null}
        kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
        ```

        Review the `rules:` section, focusing on entries where:

        ```yaml theme={null}
        apiGroups: [""]
        resources: ["persistentvolumes"]
        verbs: ["create", ...]
        ```

        **Output indicating a problem:**

        * Roles that are **broad** (e.g., `verbs: ["*"]` or `resources: ["*"]`) and include `persistentvolumes`.
        * Roles meant for general users, CI/CD, or application service accounts that also have `create` on `persistentvolumes` without a clear operational need.

        ***

        **3. Discover who actually gets these permissions (ClusterRoleBindings)**

        For each problematic ClusterRole from step 2:

        ```sh theme={null}
        kubectl get clusterrolebindings -o json | jq -r '
          .items[]
          | select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<CLUSTERROLE_NAME>")
          | .metadata.name
        '
        ```

        Without `jq`:

        ```sh theme={null}
        kubectl get clusterrolebindings -o yaml | \
          awk -v target="<CLUSTERROLE_NAME>" '
            $0 ~ /^kind: ClusterRoleBinding/ { inb=1; name="" }
            inb && $1 == "name:" { name=$2 }
            inb && $1 == "roleRef:" { inrole=1 }
            inrole && $1 == "name:" && $2 == target { print name }
            /^$/ { inb=inrole=0 }
          '
        ```

        Then inspect each binding:

        ```sh theme={null}
        kubectl get clusterrolebinding <BINDING_NAME> -o yaml
        ```

        **Output indicating a problem:**

        * Bindings that grant the ClusterRole to:
          * `system:authenticated`, `system:unauthenticated`, or large groups (e.g., `developers`, `ci-users`) where most members do not need PV creation.
          * ServiceAccounts in namespaces unrelated to storage/cluster operations.
        * Any binding where you cannot clearly justify why that subject must create PersistentVolumes.

        ***

        **4. Verification after manual changes**

        After you have manually updated or removed roles/bindings (via manifests or `kubectl edit`), re-run step 1:

        ```sh theme={null}
        kubectl get clusterroles -o json | jq -r '
          .items[]
          | select(
              (.rules // [])
              | map(
                  (.resources // []) as $r
                  | (.verbs // []) as $v
                  | select( ("persistentvolumes" | IN($r[]?)) and ("create" | IN($v[]?)) )
                )
              | length > 0
            )
          | .metadata.name
        '
        ```

        **Verification result interpretation:**

        * If no ClusterRole names are returned, then no ClusterRole currently grants `create` on `persistentvolumes`.
        * If only a small, well-justified set of highly privileged operational roles are returned, you must confirm they are appropriate; if not, further manual adjustment is required.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Report who can create PersistentVolumes (CIS Kubernetes 5.1.9)

        set -euo pipefail

        echo "=== ClusterRoles and ClusterRoleBindings with 'create' on persistentvolumes ==="
        echo

        # 1) List ClusterRoles that can create persistentvolumes
        kubectl get clusterrole -o json \
          | jq -r '
            .items[]
            | select(
                .rules // [] 
                | any(.resources[]? == "persistentvolumes"
                      and (.verbs[]? == "create" or .verbs[]? == "*"))
              )
            | .metadata.name
          ' | sort -u | while read -r cr; do
                [ -z "$cr" ] && continue
                echo "ClusterRole: ${cr}"
                kubectl get clusterrole "$cr" -o json \
                  | jq -r '
                      .rules[]
                      | select(any(.resources[]? == "persistentvolumes"))
                      | "  apiGroups: " + ( (.apiGroups // []) | join(",") )
                        + "\n  resources: " + ( (.resources // []) | join(",") )
                        + "\n  verbs: " + ( (.verbs // []) | join(",") )
                    '
                echo

                # Show who gets this ClusterRole via ClusterRoleBinding
                echo "  Bound via ClusterRoleBindings:"
                kubectl get clusterrolebinding -o json \
                  | jq -r --arg cr "$cr" '
                      .items[]
                      | select(.roleRef.kind == "ClusterRole" and .roleRef.name == $cr)
                      | "    ClusterRoleBinding: " + .metadata.name
                        + "\n      subjects: "
                        + ( if (.subjects // [] | length) == 0
                            then "NONE"
                            else
                              ( .subjects[]
                                | "        - kind: " + (.kind // "")
                                  + ", name: " + (.name // "")
                                  + (if .namespace then ", namespace: " + .namespace else "" end)
                              )
                              | join("\n")
                          end
                          )
                    '
                echo
            done

        echo "=== Roles and RoleBindings with 'create' on persistentvolumes (namespace-scoped RBAC) ==="
        echo

        # 2) List namespace-scoped Roles that can create persistentvolumes
        kubectl get role --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(
                .rules // [] 
                | any(.resources[]? == "persistentvolumes"
                      and (.verbs[]? == "create" or .verbs[]? == "*"))
              )
            | .metadata.namespace + " " + .metadata.name
          ' | sort -u | while read -r ns name; do
                [ -z "$ns" ] && continue
                echo "Role: ${name} (namespace: ${ns})"
                kubectl get role "$name" -n "$ns" -o json \
                  | jq -r '
                      .rules[]
                      | select(any(.resources[]? == "persistentvolumes"))
                      | "  apiGroups: " + ( (.apiGroups // []) | join(",") )
                        + "\n  resources: " + ( (.resources // []) | join(",") )
                        + "\n  verbs: " + ( (.verbs // []) | join(",") )
                    '
                echo

                # Show who gets this Role via RoleBinding
                echo "  Bound via RoleBindings:"
                kubectl get rolebinding -n "$ns" -o json \
                  | jq -r --arg name "$name" '
                      .items[]
                      | select(.roleRef.kind == "Role" and .roleRef.name == $name)
                      | "    RoleBinding: " + .metadata.name
                        + "\n      subjects: "
                        + ( if (.subjects // [] | length) == 0
                            then "NONE"
                            else
                              ( .subjects[]
                                | "        - kind: " + (.kind // "")
                                  + ", name: " + (.name // "")
                                  + (if .namespace then ", namespace: " + .namespace else "" end)
                              )
                              | join("\n")
                          end
                          )
                    '
                echo
            done

        echo "=== Summary: subjects that can create PersistentVolumes (cluster-scoped) ==="
        echo

        # 3) Compact subject summary for ClusterRoles
        kubectl get clusterrole -o json \
          | jq -r '
            .items[]
            | select(
                .rules // [] 
                | any(.resources[]? == "persistentvolumes"
                      and (.verbs[]? == "create" or .verbs[]? == "*"))
              )
            | .metadata.name
          ' | sort -u | while read -r cr; do
                [ -z "$cr" ] && continue
                kubectl get clusterrolebinding -o json \
                  | jq -r --arg cr "$cr" '
                      .items[]
                      | select(.roleRef.kind == "ClusterRole" and .roleRef.name == $cr)
                      | .subjects[]? 
                      | $cr + " -> " + (.kind // "") + "/" + (.name // "") 
                        + (if .namespace then " (ns:" + .namespace + ")" else "" end)
                  '
            done | sort -u
        ```

        **How to run (any machine with kubectl access):**

        * Save as `report-pv-create-access.sh`
        * Make executable: `chmod +x report-pv-create-access.sh`
        * Run: `./report-pv-create-access.sh`

        **What indicates a problem:**

        * Any `ClusterRole` or `Role` whose rules include:
          * `resources: persistentvolumes` with `verbs` containing `create` or `*`.
        * Especially concerning:
          * These roles bound to broad subjects, for example:
            * `kind: Group, name: system:authenticated`
            * `kind: Group, name: system:masters` (if that group is widely used)
            * ServiceAccounts used by application workloads rather than storage controllers.
        * For the summary section, any line like:
          * `cluster-admin -> Group/system:authenticated`
          * `some-pv-manager -> ServiceAccount/default (ns:default)`
            should be reviewed to confirm that subject truly requires PV creation rights.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
