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

# Pods Should Not Mount HostPath Volumes

### More Info:

Verifies no pod mounts a hostPath volume. hostPath exposes the node filesystem to the pod and can be used to escape to the host.

### Risk Level

High

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. On any machine with kubectl access, list all pods using hostPath volumes (excluding system namespaces) and note which are controlled by higher-level resources (Deployment/DaemonSet/StatefulSet/Job/etc.):

           ```bash theme={null}
           kubectl get pods --all-namespaces -o json | jq -r '
             [ .items[]
             | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
             | .metadata as $m
             | [ (.spec.volumes // [])[] | select(.hostPath != null) ] as $hp
             | [ ($m.ownerReferences // [])[] | select(.controller) ] | first as $own
             | select(($hp | length) > 0)
             | "\($m.namespace) \($m.name) \($own.kind // "Pod") \($own.name // $m.name)"
             ][]'
           ```

        2. For each offending pod that is controlled by a workload resource (e.g., Deployment), edit the controller manifest and remove/replace the `hostPath` volumes. Example for a Deployment:

           ```bash theme={null}
           kubectl -n NAMESPACE edit deployment CONTROLLER_NAME
           ```

           In the editor:

           * Under `spec.template.spec.volumes`, delete any entries containing `hostPath:` or replace them with `emptyDir: {}` or a PersistentVolume/PVC reference as appropriate.
           * Under `spec.template.spec.containers[].volumeMounts`, remove the corresponding `name:` entries or adjust them to point to the new volume type.
             Save and exit; Kubernetes will roll out new pods without hostPath.

        3. For pods created directly (no controller) using hostPath, export, modify, and reapply their manifests:

           ```bash theme={null}
           kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod-with-hostpath.yaml
           ```

           Edit `/tmp/pod-with-hostpath.yaml`:

           * Remove the entire `metadata:` block fields `resourceVersion`, `uid`, `selfLink`, `creationTimestamp`, `status`, and any `ownerReferences`.
           * Under `spec.volumes`, remove or replace any entries containing `hostPath:` with `emptyDir: {}` or a PV/PVC.
           * Update `spec.containers[].volumeMounts` to remove or point away from those volume names.

           Then delete and recreate the pod:

           ```bash theme={null}
           kubectl -n NAMESPACE delete pod POD_NAME
           kubectl -n NAMESPACE apply -f /tmp/pod-with-hostpath.yaml
           ```

        4. For workloads that legitimately need node storage, create and use PersistentVolumes and PersistentVolumeClaims instead of hostPath (on any machine with kubectl access). Example (adapt for your case) to create a PVC and reference it from the workload:

           ```bash theme={null}
           cat <<'EOF' > /tmp/pvc.yaml
           apiVersion: v1
           kind: PersistentVolumeClaim
           metadata:
             name: app-pvc
             namespace: NAMESPACE
           spec:
             accessModes:
               - ReadWriteOnce
             resources:
               requests:
                 storage: 10Gi
             storageClassName: standard
           EOF

           kubectl apply -f /tmp/pvc.yaml
           ```

           Then in the controller manifest (`kubectl -n NAMESPACE edit deployment CONTROLLER_NAME`), use:

           ```yaml theme={null}
           spec:
             template:
               spec:
                 volumes:
                   - name: app-storage
                     persistentVolumeClaim:
                       claimName: app-pvc
                 containers:
                   - name: APP_CONTAINER
                     volumeMounts:
                       - name: app-storage
                         mountPath: /path/in/container
           ```

        5. For any third-party or GKE-managed add-ons that appear to use hostPath, review their documentation and only modify them if they are not required or if the vendor provides a hostPath-free configuration. If you must keep a hostPath-based add-on, document the risk and namespace, labels, and pod names for risk acceptance:

           ```bash theme={null}
           kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/justify-hostpath-POD_NAME.yaml
           ```

        6. Verification (on any machine with kubectl access): rerun the audit command and confirm there are no lines with `is_compliant=false` and no `hostPaths=` fields:

           ```bash theme={null}
           kubectl get pods --all-namespaces -o json | jq -r '
             [ .items[]
             | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
             | .metadata as $m
             | (.spec.nodeName // "") as $node
             | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
             | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
             | [ (.spec.volumes // [])[] | select(.hostPath != null) ] as $hp
             | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
               + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
               + (if $node   == ""   then "" else " node=\($node)" end)
               + (if $labels == ""   then "" else " labels=\($labels)" end)
               + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
               + (if ($hp | length) == 0 then "" else " hostPaths=\([ $hp[] | .hostPath.path ] | join("+"))" end)
               + " is_compliant=\(if ($hp | length) > 0 then "false" else "true" end)"
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```

           The cluster is compliant when the output is either `is_compliant=true` only, or all listed pods show `is_compliant=true` and no `hostPaths=`.
      </Accordion>

      <Accordion title="Using kubectl">
        On any machine with kubectl access:

        1. Identify offending pods and their owners

        ```bash theme={null}
        kubectl get pods --all-namespaces -o json | jq -r '
          [ .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | .metadata as $m
          | (.spec.nodeName // "") as $node
          | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | [ (.spec.volumes // [])[] | select(.hostPath != null) ] as $hp
          | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
            + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
            + (if $node   == ""   then "" else " node=\($node)" end)
            + (if $labels == ""   then "" else " labels=\($labels)" end)
            + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
            + (if ($hp | length) == 0 then "" else " hostPaths=\([ $hp[] | .hostPath.path ] | join("+"))" end)
            + " is_compliant=\(if ($hp | length) > 0 then "false" else "true" end)"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        ```

        For each non‑compliant line, note:

        * `ns=<namespace>`
        * `owner=<Kind>/<namespace>/<name>/...` if present; otherwise treat it as a standalone Pod.

        2. Edit the controller manifest to remove `hostPath`

        For a Deployment (example):

        ```bash theme={null}
        kubectl -n NAMESPACE get deploy DEPLOYMENT_NAME -o yaml > /tmp/deploy-no-hostpath.yaml
        ```

        Edit `/tmp/deploy-no-hostpath.yaml`:

        * In `spec.template.spec.volumes`, delete any entries with `hostPath:`.
        * In `spec.template.spec.containers[].volumeMounts`, remove mounts that referenced those `hostPath` volumes.
        * If needed, add a safer volume type, for example:

        ```yaml theme={null}
        spec:
          template:
            spec:
              volumes:
                - name: app-data
                  emptyDir: {}
              containers:
                - name: app
                  volumeMounts:
                    - name: app-data
                      mountPath: /app/data
        ```

        Apply the updated manifest:

        ```bash theme={null}
        kubectl apply -f /tmp/deploy-no-hostpath.yaml
        ```

        Repeat the same pattern for other controller types as applicable:

        * ReplicaSet: `kubectl -n NAMESPACE get rs NAME -o yaml > /tmp/rs-no-hostpath.yaml`
        * StatefulSet: `kubectl -n NAMESPACE get sts NAME -o yaml > /tmp/sts-no-hostpath.yaml`
        * DaemonSet: `kubectl -n NAMESPACE get ds NAME -o yaml > /tmp/ds-no-hostpath.yaml`
        * Job/CronJob: `kubectl -n NAMESPACE get job|cronjob NAME -o yaml > /tmp/job-no-hostpath.yaml`

        Then edit to remove `hostPath` volumes and corresponding mounts, replace with `emptyDir`, PersistentVolumeClaims, or projected volumes, and:

        ```bash theme={null}
        kubectl apply -f /tmp/<edited-file>.yaml
        ```

        3. For standalone Pods (no controller)

        Export, edit, and recreate without `hostPath`:

        ```bash theme={null}
        kubectl -n NAMESPACE get pod POD_NAME -o yaml \
          | sed '/^[ ]*resourceVersion:/d;/^[ ]*uid:/d;/^[ ]*creationTimestamp:/d;/^[ ]*selfLink:/d;/^[ ]*managedFields:/d;/^[ ]*status:/,$d' \
          > /tmp/pod-no-hostpath.yaml
        ```

        Edit `/tmp/pod-no-hostpath.yaml` to:

        * Remove `hostPath` entries under `spec.volumes`.
        * Remove corresponding `volumeMounts`.
        * Optionally add safer volume types as above.

        Delete and recreate:

        ```bash theme={null}
        kubectl -n NAMESPACE delete pod POD_NAME
        kubectl apply -f /tmp/pod-no-hostpath.yaml
        ```

        4. Verification

        Run the original audit command and confirm either `is_compliant=true` only, or that no lines show `hostPaths=`:

        ```bash theme={null}
        kubectl get pods --all-namespaces -o json | jq -r '
          [ .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | .metadata as $m
          | (.spec.nodeName // "") as $node
          | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | [ (.spec.volumes // [])[] | select(.hostPath != null) ] as $hp
          | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
            + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
            + (if $node   == ""   then "" else " node=\($node)" end)
            + (if $labels == ""   then "" else " labels=\($labels)" end)
            + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
            + (if ($hp | length) == 0 then "" else " hostPaths=\([ $hp[] | .hostPath.path ] | join("+"))" end)
            + " is_compliant=\(if ($hp | length) > 0 then "false" else "true" end)"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        ```
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Remediation script for: Pods Should Not Mount HostPath Volumes (CBP C1.7)
        # Platform: GKE (managed)
        # Surface: Kubernetes API objects via kubectl/manifests
        #
        # Runs on: any machine with kubectl access to the cluster/context.
        #
        # Behavior:
        # - Scans all namespaces (except kube-system, kube-public, kube-node-lease)
        #   for pods using hostPath volumes.
        # - For each owning controller (Deployment/DaemonSet/StatefulSet/Job/CronJob/ReplicaSet/ReplicationController)
        #   it writes a patched manifest with hostPath volumes removed into ./backup-hostpath-fix
        #   but DOES NOT APPLY ANY CHANGES AUTOMATICALLY.
        # - Prints next-step instructions.
        # - Re-runs the audit command to verify once you have applied the changes.
        #
        # This is idempotent: re-running overwrites the backup/patch files for the
        # same controllers and re-runs the audit.
        #
        # IMPORTANT:
        # - Removal of hostPath volumes is an application-level change and must be
        #   evaluated by the owning team. This script only prepares manifests to edit.
        # - You must manually replace hostPath with a safer volume type
        #   (PersistentVolume, emptyDir, projected volumes, etc.) before applying.

        set -euo pipefail

        BACKUP_DIR="./backup-hostpath-fix"
        mkdir -p "${BACKUP_DIR}"

        echo "=== Step 1: Discover pods using hostPath volumes (excluding system namespaces) ==="

        # Store audit output for later comparison
        AUDIT_BEFORE="${BACKUP_DIR}/audit-before.txt"

        kubectl get pods --all-namespaces -o json | jq -r '
          [ .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | .metadata as $m
          | (.spec.nodeName // "") as $node
          | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | [ (.spec.volumes // [])[] | select(.hostPath != null) ] as $hp
          | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
            + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
            + (if $node   == ""   then "" else " node=\($node)" end)
            + (if $labels == ""   then "" else " labels=\($labels)" end)
            + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
            + (if ($hp | length) == 0 then "" else " hostPaths=\([ $hp[] | .hostPath.path ] | join("+"))" end)
            + " is_compliant=\(if ($hp | length) > 0 then "false" else "true" end)"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end' | tee "${AUDIT_BEFORE}"

        # Extract controllers that own non-compliant pods
        echo
        echo "=== Step 2: Identify owning controllers for non-compliant pods ==="

        # jq filter to output: namespace kind name for each unique controller
        CONTROLLERS_FILE="${BACKUP_DIR}/controllers.txt"

        grep "is_compliant=false" "${AUDIT_BEFORE}" || true

        kubectl get pods --all-namespaces -o json | jq -r '
          .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | [ (.spec.volumes // [])[] | select(.hostPath != null) ] as $hp
          | select(($hp | length) > 0)
          | [ (.metadata.ownerReferences // [])[] | select(.controller) ] | first as $own
          | .metadata.namespace as $ns
          | if $own == null then
              # Standalone pod
              "\($ns) Pod \(.metadata.name)"
            else
              "\($ns) \($own.kind) \($own.name)"
            end
        ' | sort -u > "${CONTROLLERS_FILE}"

        if [[ ! -s "${CONTROLLERS_FILE}" ]]; then
          echo "No non-compliant pods discovered (or only in system namespaces)."
          echo "Cluster appears compliant for this control."
          exit 0
        fi

        echo "Controllers (or standalone Pods) owning pods with hostPath volumes:"
        cat "${CONTROLLERS_FILE}"

        echo
        echo "=== Step 3: Export current manifests for review and patch preparation ==="
        echo "Manifests will be written under: ${BACKUP_DIR}"
        echo

        while read -r NS KIND NAME; do
          SAFE_KIND="${KIND}"
          # Normalize common aliases to full kinds (kubectl handles short names but keep it explicit in filenames)
          case "${KIND}" in
            ds|daemonset|Daemonset) SAFE_KIND="DaemonSet" ;;
            deploy|deployment|Deployment) SAFE_KIND="Deployment" ;;
            sts|statefulset|Statefulset) SAFE_KIND="StatefulSet" ;;
            rs|replicaset|ReplicaSet) SAFE_KIND="ReplicaSet" ;;
            rc|replicationcontroller|Replicationcontroller) SAFE_KIND="ReplicationController" ;;
            job|Job) SAFE_KIND="Job" ;;
            cj|cronjob|Cronjob) SAFE_KIND="CronJob" ;;
            pod|Pod) SAFE_KIND="Pod" ;;
          esac

          OUT_BASE="${BACKUP_DIR}/${NS}__${SAFE_KIND}__${NAME}"
          MANIFEST_ORIG="${OUT_BASE}-original.yaml"
          MANIFEST_PATCH="${OUT_BASE}-hostpath-removed.yaml"

          echo "Exporting ${SAFE_KIND} ${NS}/${NAME} to ${MANIFEST_ORIG}"

          # Export current manifest
          if ! kubectl get "${SAFE_KIND}" "${NAME}" -n "${NS}" -o yaml > "${MANIFEST_ORIG}" 2>/dev/null; then
            echo "WARNING: Failed to get ${SAFE_KIND} ${NS}/${NAME}. It may have been deleted or the kind is unsupported. Skipping."
            continue
          fi

          # Prepare a version with hostPath volumes removed, for manual review/edit.
          # This removes any volumes with hostPath and their corresponding volumeMounts in containers.
          echo "Preparing tentative hostPath-removed manifest at ${MANIFEST_PATCH}"

          # Use yq if available, otherwise just copy and let user edit manually.
          if command -v yq >/dev/null 2>&1; then
            yq '
              # Remove hostPath volumes
              (.. | select(has("volumes"))).volumes |=
                ( . // [] | map(select(.hostPath == null)) ) |
              # For each container, remove volumeMounts that refer to deleted volumes
              . as $root |
              def strip_mounts(path):
                (.spec.template.spec.containers // []) as $c |
                (.spec.template.spec.volumes // []) as $v |
                .spec.template.spec.containers |=
                  ( $c
                    | map(
                        .volumeMounts |=
                          ( . // [] |
                            map(select(.name as $n | ($v | map(.name) | index($n)) != null))
                          )
                      )
                  ) ;
              # For standalone Pod
              if .kind == "Pod" then
                (.spec.containers // []) as $c |
                (.spec.volumes // []) as $v |
                .spec.containers |=
                  ( $c
                    | map(
                        .volumeMounts |=
                          ( . // [] |
                            map(select(.name as $n | ($v | map(.name) | index($n)) != null))
                          )
                      )
                  )
              else
                strip_mounts(.)
              end
            ' "${MANIFEST_ORIG}" > "${MANIFEST_PATCH}"
          else
            cp "${MANIFEST_ORIG}" "${MANIFEST_PATCH}"
            echo "NOTE: yq not found; ${MANIFEST_PATCH} is a copy of the original."
            echo "      Manually edit it to remove hostPath volumes and corresponding mounts."
          fi

        done < "${CONTROLLERS_FILE}"

        cat <<'EOF'

        === Step 4: Manual review & application required ===

        For each generated *-hostpath-removed.yaml file in ./backup-hostpath-fix:

        1. Open the file and ensure all `hostPath` volumes have been removed.
        2. Replace them with safer volume types where needed:
           - PersistentVolumeClaim (PVC) backed storage
           - `emptyDir`
           - projected/configMap/secret volumes
        3. Confirm the resulting spec is acceptable for the workload owner.
        4. Apply the manifest to the cluster, for example:
           kubectl apply -f ./backup-hostpath-fix/<namespace>__<Kind>__<name>-hostpath-removed.yaml

        This will update the controller and cause pods to be recreated without hostPath volumes.

        EOF

        echo "=== Step 5: Verification (re-run audit) ==="
        echo "After you have applied the updated manifests, run the following command to verify:"
        echo
        cat <<'EOF'
        kubectl get pods --all-namespaces -o json | jq -r '
          [ .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | .metadata as $m
          | (.spec.nodeName // "") as $node
          | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | [ (.spec.volumes // [])[] | select(.hostPath != null) ] as $hp
          | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
            + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
            + (if $node   == ""   then "" else " node=\($node)" end)
            + (if $labels == ""   then "" else " labels=\($labels)" end)
            + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
            + (if ($hp | length) == 0 then "" else " hostPaths=\([ $hp[] | .hostPath.path ] | join("+"))" end)
            + " is_compliant=\(if ($hp | length) > 0 then "false" else "true" end)"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        EOF

        echo
        echo "Compliance target: no line with 'is_compliant=false' outside system namespaces."
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
