> ## 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 PersistentVolume Objects

### More Info:

Create access to PersistentVolume objects can be used to mount host paths and access node data. Restrict this permission to trusted administrators only.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* CIS EKS

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **List all principals with PV create rights**
           * Run on: any machine with kubectl access
           * Command:
             ```bash theme={null}
             kubectl get clusterrole -o json \
               | jq -r '
                 .items[]
                 | select(
                     .rules[]
                     | select(
                         ((.apiGroups // []) | index("") != null)
                         and ((.resources // []) | index("persistentvolumes") != null)
                         and ((.verbs // []) | index("create") != null)
                     )
                   )
                 | .metadata.name
               ' | sort -u
             ```
           * Save the resulting ClusterRole names for review.

        2. **Review each ClusterRole’s permissions and intended use**
           * Run for each ClusterRole name from step 1:
             ```bash theme={null}
             kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
             ```
           * Manually confirm:
             * Why this role needs to create `persistentvolumes` (if at all).
             * Whether it is intended only for cluster administrators or for broader use.

        3. **Identify which subjects are bound to those ClusterRoles**
           * Run:
             ```bash theme={null}
             kubectl get clusterrolebindings -o json \
               | jq -r '
                 .items[]
                 | select(.roleRef.kind=="ClusterRole"
                          and (.roleRef.name | IN({"'$(kubectl get clusterrole -o json | jq -r ".items[].metadata.name" | paste -sd'","' -)'" })))
                 | .metadata.name + " " + .roleRef.name
               '
             ```
           * Or, per role for clarity:
             ```bash theme={null}
             kubectl get clusterrolebinding -o json \
               | jq -r '
                 .items[]
                 | select(.roleRef.kind=="ClusterRole" and .roleRef.name=="<CLUSTERROLE_NAME>")
                 | .metadata.name + " " + (.subjects // [] | map(.kind+":"+(.namespace // "-")+":"+.name) | join(","))
               '
             ```
           * Manually assess whether each user/group/service account truly requires PV creation.

        4. **Decide and implement RBAC tightening**\
           For each ClusterRole / subject combination where PV creation is not strictly required:
           * **Option A – Remove the binding for non-admin subjects** (preferred when only some subjects should lose access):
             * Edit binding:
               ```bash theme={null}
               kubectl edit clusterrolebinding <BINDING_NAME>
               ```
             * Remove untrusted subjects from `.subjects`.
           * **Option B – Remove PV create from the ClusterRole** (only if no remaining subject needs it):
             * Edit role:
               ```bash theme={null}
               kubectl edit clusterrole <CLUSTERROLE_NAME>
               ```
             * In `rules`, remove `persistentvolumes` from `resources` or `create` from `verbs`, as appropriate.

        5. **Optionally create a dedicated admin-only PV role**
           * If PV creation is needed only for cluster admins, define a narrowly scoped role and bind it only to trusted administrators:
             ```bash theme={null}
             cat <<'EOF' | kubectl apply -f -
             apiVersion: rbac.authorization.k8s.io/v1
             kind: ClusterRole
             metadata:
               name: pv-admin
             rules:
             - apiGroups: [""]
               resources: ["persistentvolumes"]
               verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
             EOF
             ```
           * Bind it only to your administrator group or user:
             ```bash theme={null}
             cat <<'EOF' | kubectl apply -f -
             apiVersion: rbac.authorization.k8s.io/v1
             kind: ClusterRoleBinding
             metadata:
               name: pv-admin-binding
             roleRef:
               apiGroup: rbac.authorization.k8s.io
               kind: ClusterRole
               name: pv-admin
             subjects:
             - kind: User
               name: admin@example.com
             EOF
             ```

        6. **Verify current PV create access after changes**
           * Re-run the discovery from step 1 to ensure only intended ClusterRoles retain PV `create`:
             ```bash theme={null}
             kubectl get clusterrole -o json \
               | jq -r '
                 .items[]
                 | select(
                     .rules[]
                     | select(
                         ((.apiGroups // []) | index("") != null)
                         and ((.resources // []) | index("persistentvolumes") != null)
                         and ((.verbs // []) | index("create") != null)
                     )
                   )
                 | .metadata.name
               ' | sort -u
             ```
           * Confirm that every remaining role with PV create is explicitly justified and bound only to trusted administrators.
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 1) List all roles/clusterroles that can create PersistentVolumes
        # Run on: any machine with kubectl access

        kubectl get clusterrole -ojson \
          | jq -r '
            .items[]
            | select(
                (.rules // [])
                | map(
                    (.resources // []) | index("persistentvolumes")
                    and
                    (.verbs // []) | (index("create") or index("*"))
                )
                | any
              )
            | .metadata.name' | sort

        kubectl get role -A -ojson \
          | jq -r '
            .items[]
            | select(
                (.rules // [])
                | map(
                    (.resources // []) | index("persistentvolumes")
                    and
                    (.verbs // []) | (index("create") or index("*"))
                )
                | any
              )
            | (.metadata.namespace + "/" + .metadata.name)' | sort
        ```

        **Problem indication:** Any `ClusterRole` or namespaced `Role` in this list grants `create` (or `*`) on `persistentvolumes`. These are candidates for review; broad or non-admin sounding names (e.g. `developer`, `ci`, `default`, `edit`, `view`) are usually problematic.

        ```bash theme={null}
        # 2) Inspect each identified role/clusterrole in detail
        # Replace <clusterrole-name> and <namespace>/<role-name> from step 1

        # ClusterRole detail (rules, who should have this?)
        kubectl get clusterrole <clusterrole-name> -o yaml

        # Role detail
        kubectl get role -n <namespace> <role-name> -o yaml
        ```

        **Problem indication:** In the `rules` section, look for:

        * `resources: ["persistentvolumes"]` with `verbs` including `create` or `*`.
        * Very wide resources/verbs (e.g. `resources: ["*"]`, `verbs: ["*"]`) assigned to non-admin use cases.

        ```bash theme={null}
        # 3) Find who is bound to these roles/clusterroles
        # For each role/clusterrole from step 1

        # ClusterRoleBindings for a given ClusterRole
        kubectl get clusterrolebindings.rbac.authorization.k8s.io -ojson \
          | jq -r '
            .items[]
            | select(.roleRef.kind=="ClusterRole" and .roleRef.name=="<clusterrole-name>")
            | .metadata.name' | sort

        # RoleBindings for a given Role (namespaced)
        kubectl get rolebindings.rbac.authorization.k8s.io -n <namespace> -ojson \
          | jq -r '
            .items[]
            | select(.roleRef.kind=="Role" and .roleRef.name=="<role-name>")
            | .metadata.name' | sort

        # Inspect each binding to see subjects (users, groups, service accounts)
        kubectl get clusterrolebinding <binding-name> -o yaml
        kubectl get rolebinding -n <namespace> <binding-name> -o yaml
        ```

        **Problem indication:** In each binding’s `subjects`:

        * Non-admin users/groups (e.g. generic app teams, CI service accounts, default service accounts) bound to roles that can create `persistentvolumes`.
        * Wildcard or overly broad groups (e.g. `system:authenticated`) having such roles.

        ```bash theme={null}
        # 4) Cluster-wide review snapshot (optional quick overview)
        # Shows all create-PV permissions and their subjects in one place (read-only)
        kubectl get clusterrole,role -A -ojson \
          | jq -r '
            .items[]
            | . as $role
            | (.rules // [])
            | map(
                select(
                  (.resources // []) | index("persistentvolumes")
                  and
                  (.verbs // []) | (index("create") or index("*"))
                )
              )
            | select(length>0)
            | $role.kind + " " +
              (if $role.kind=="ClusterRole" then $role.metadata.name
               else ($role.metadata.namespace + "/" + $role.metadata.name)
               end)' | while read kind name; do
            echo "=== $kind $name ==="
            if [ "$kind" = "ClusterRole" ]; then
              kubectl get clusterrolebinding -ojson \
                | jq -r --arg r "$name" '
                    .items[]
                    | select(.roleRef.kind=="ClusterRole" and .roleRef.name==$r)
                    | "CRB: " + .metadata.name + " subjects=" + (.subjects // [] | map(.kind+":"+(.namespace // "")+":"+.name) | join(","))'
            else
              ns="${name%%/*}"; rn="${name##*/}"
              kubectl get rolebinding -n "$ns" -ojson \
                | jq -r --arg r "$rn" '
                    .items[]
                    | select(.roleRef.kind=="Role" and .roleRef.name==$r)
                    | "RB: " + .metadata.name + " subjects=" + (.subjects // [] | map(.kind+":"+(.namespace // "")+":"+.name) | join(","))'
            fi
            echo
          done
        ```

        **Problem indication:** Any role/clusterrole that allows `create` on `persistentvolumes` and is bound to non-trusted administrator identities should be flagged for human review and potential restriction.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Report all subjects that can create PersistentVolumes (pv) cluster-wide.
        # Run on: any machine with kubectl access and current kube-context set to the target cluster.

        set -euo pipefail

        # 1. Show all ClusterRoles/Roles that grant create on persistentvolumes
        echo "=== RBAC rules granting create on persistentvolumes ==="
        kubectl get clusterrole,role -A -o json | \
          jq -r '
            .items[]
            | {
                kind: .kind,
                namespace: (.metadata.namespace // ""),
                name: .metadata.name,
                rules: (.rules // [])
              }
            | select(
                .rules[]
                | select(
                    ((.resources // []) | index("persistentvolumes")) and
                    ((.verbs // []) | index("create"))
                  )
              )
            | "\(.kind)\t\(.namespace)\t\(.name)"
          ' | sort -u | column -t

        echo
        echo "=== ClusterRole/Role details for those entries ==="
        kubectl get clusterrole,role -A -o json | \
          jq -r '
            .items[]
            | {
                kind: .kind,
                namespace: (.metadata.namespace // ""),
                name: .metadata.name,
                rules: (.rules // [])
              } as $r
            | select(
                $r.rules[]
                | select(
                    ((.resources // []) | index("persistentvolumes")) and
                    ((.verbs // []) | index("create"))
                  )
              )
            | (
                "-----",
                "KIND: \($r.kind)",
                "NAMESPACE: \($r.namespace)",
                "NAME: \($r.name)",
                "RULES:",
                (
                  $r.rules[]
                  | select(
                      ((.resources // []) | index("persistentvolumes")) and
                      ((.verbs // []) | index("create"))
                    )
                  | "  apiGroups: \(.apiGroups // [])",
                    "  resources: \(.resources // [])",
                    "  verbs: \(.verbs // [])"
                )
              )
          '

        # 2. List all bindings that reference those ClusterRoles/Roles
        echo
        echo "=== RoleBindings/ClusterRoleBindings that use those roles ==="
        # First, collect role names into a temp file for easy re-use.
        TMP_ROLES="$(mktemp)"
        kubectl get clusterrole,role -A -o json | \
          jq -r '
            .items[]
            | {
                kind: .kind,
                namespace: (.metadata.namespace // ""),
                name: .metadata.name,
                rules: (.rules // [])
              } as $r
            | select(
                $r.rules[]
                | select(
                    ((.resources // []) | index("persistentvolumes")) and
                    ((.verbs // []) | index("create"))
                  )
              )
            | "\($r.kind),\($r.namespace),\($r.name)"
          ' | sort -u > "${TMP_ROLES}"

        if [[ ! -s "${TMP_ROLES}" ]]; then
          echo "No roles or clusterroles grant create on persistentvolumes."
          rm -f "${TMP_ROLES}"
          exit 0
        fi

        echo "Roles that can create persistentvolumes:"
        cat "${TMP_ROLES}"
        echo

        echo "Bindings:"
        kubectl get clusterrolebinding,rolebinding -A -o json | \
          jq -r --argfile roles "${TMP_ROLES}" '
            def role_match($ref):
              ($roles[] | split(",") ) as $r
              | ($r[0] == ($ref.kind) and $r[2] == ($ref.name)
                 and ( ( ($ref.kind == "Role") and ($ref.namespace == $r[1]) )
                       or ($ref.kind == "ClusterRole") ) );

            .items[]
            | {
                kind: .kind,
                namespace: (.metadata.namespace // ""),
                name: .metadata.name,
                roleRef: .roleRef,
                subjects: (.subjects // [])
              } as $b
            | select(
                ($b.kind == "ClusterRoleBinding" and
                 role_match({kind:"ClusterRole", name:$b.roleRef.name, namespace:""}))
                or
                ($b.kind == "RoleBinding" and
                 role_match({kind:"Role", name:$b.roleRef.name, namespace:$b.namespace}))
              )
            | (
                "-----",
                "BINDING KIND: \($b.kind)",
                "BINDING NAMESPACE: \($b.namespace)",
                "BINDING NAME: \($b.name)",
                "  roleRef: \($b.roleRef.kind)/\($b.roleRef.name)",
                "  subjects:",
                (
                  if ($b.subjects | length) == 0 then
                    "    (none)"
                  else
                    ($b.subjects[] |
                      "    - kind: \(.kind)",
                      "      name: \(.name)",
                      (if .namespace then "      namespace: \(.namespace)" else empty end)
                    )
                  end
                )
              )
          '

        rm -f "${TMP_ROLES}"

        echo
        echo "=== INTERPRETATION ==="
        echo "Any ClusterRole/Role listed above that is bound to broad subjects"
        echo "(e.g. system:authenticated, system:serviceaccounts, default service accounts,"
        echo "or non-admin users/groups) represents a potential problem."
        echo
        echo "Manually review:"
        echo "  - Whether each subject truly needs to create PersistentVolumes."
        echo "  - Whether the binding scope can be narrowed or the permission removed."
        ```

        **How to use and what indicates a problem**

        * Run the script on any machine with `kubectl` and access to the cluster.
        * Focus on:
          * ClusterRoles/Rules that include `resources: ["persistentvolumes"]` and `verbs` including `create`.
          * Bindings where those roles are granted to:
            * Wide groups like `system:authenticated`, `system:serviceaccounts`, or `system:unauthenticated`.
            * Namespace default service accounts (e.g. `kind: ServiceAccount`, `name: default`).
            * Regular users/groups that are not trusted cluster administrators.

        Any such combination (privileged role + broad/non-admin subject) is the condition that needs manual review and likely restriction, per the benchmark.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
