> ## 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. List all non-system pods that mount `hostPath` volumes (run on any machine with kubectl access):
           ```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'
           ```

        2. For each non-compliant pod, identify the owning object (Deployment/StatefulSet/DaemonSet/Job, etc.) from the `owner=` field, then export its manifest (example for a Deployment; run on any machine with kubectl access):
           ```bash theme={null}
           kubectl -n <namespace> get deployment <name> -o yaml > /tmp/<namespace>-<name>.yaml
           ```

        3. Edit the saved manifest to remove `hostPath` volumes and use compliant alternatives (run on any machine with kubectl access):
           * In `spec.template.spec.volumes`, delete each entry that contains `hostPath:`.
           * Add replacement volumes such as:
             * `emptyDir: {}` for ephemeral storage, or
             * a `persistentVolumeClaim:` that references a suitable PVC, or
             * a projected/secret/configMap volume as appropriate.
           * Update `spec.template.spec.containers[*].volumeMounts` to reference the new volume names and paths instead of the removed `hostPath` volumes.
             Save the file when done.

        4. Apply the updated manifest and let Kubernetes recreate pods without `hostPath` (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl apply -f /tmp/<namespace>-<name>.yaml
           ```

        5. For any standalone Pods (no controller owner) that use `hostPath`, delete and recreate them from corrected manifests that do not define `hostPath` volumes (run on any machine with kubectl access):
           ```bash theme={null}
           # Export, edit to remove hostPath and add compliant volumes, then:
           kubectl delete pod -n <namespace> <pod-name>
           kubectl apply -f /path/to/updated-pod.yaml
           ```

        6. Verify no remaining non-exempt pods use `hostPath` volumes (run on any machine with kubectl access):
           ```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
             | select(($hp | length) > 0)
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] | .metadata.name end'
           ```
           The cluster is compliant when the command outputs only `is_compliant=true`.
      </Accordion>

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

        1. Identify noncompliant pods (excluding AKS control namespaces):

        ```sh 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'
        ```

        2. For each offending pod, edit the owning workload (Deployment, StatefulSet, DaemonSet, Job, etc.) to remove `hostPath` volumes and use compliant alternatives.

        Example: replace a `hostPath` with `emptyDir` in a Deployment.

        a. Fetch the existing Deployment manifest:

        ```sh theme={null}
        kubectl get deploy my-app -n my-namespace -o yaml > my-app-deploy.yaml
        ```

        b. In `my-app-deploy.yaml`, find and change:

        ```yaml theme={null}
        spec:
          template:
            spec:
              volumes:
                - name: app-data
                  hostPath:
                    path: /var/lib/my-app
                    type: DirectoryOrCreate
        ```

        to, for example, an `emptyDir`:

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

        Or to a PersistentVolumeClaim you have created:

        ```yaml theme={null}
        spec:
          template:
            spec:
              volumes:
                - name: app-data
                  persistentVolumeClaim:
                    claimName: my-app-pvc
        ```

        c. Apply the updated manifest:

        ```sh theme={null}
        kubectl apply -f my-app-deploy.yaml
        ```

        3. For standalone Pods defined by manifests, edit the Pod YAML similarly: remove each `hostPath` under `.spec.volumes[]` and replace with `emptyDir`, a PVC, or another non-hostPath volume type, then re-create the Pod:

        ```sh theme={null}
        kubectl delete pod my-pod -n my-namespace
        kubectl apply -f my-pod.yaml
        ```

        4. Verification (on any machine with kubectl access):

        ```sh 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 for: Pods Should Not Mount HostPath Volumes (CBP C1.7)
        # Scope: any machine with kubectl access to the AKS cluster
        #
        # This script:
        #   1. Detects non-system Pods using hostPath volumes.
        #   2. Saves their YAML for manual remediation (no automatic rewrite, as the
        #      benchmark requires you to choose an appropriate alternative volume type).
        #   3. Optionally deletes the offending Pods or their controllers when approved.
        #   4. Re-runs the audit query to verify that no remaining Pods use hostPath.
        #
        # Idempotency:
        #   - It only acts on currently non-compliant Pods.
        #   - It skips system namespaces kube-system, kube-public, kube-node-lease.
        #   - It is safe to re-run; previously handled resources are ignored if removed.
        #
        # REQUIREMENTS:
        #   - kubectl configured for the AKS cluster.
        #   - jq installed.
        #
        # USAGE:
        #   ./fix_hostpath_pods.sh             # detect and export manifests only
        #   DRY_RUN=false ./fix_hostpath_pods.sh   # also delete Pods/controllers after confirmation
        #
        set -euo pipefail

        DRY_RUN="${DRY_RUN:-true}"   # default to detection/export only
        EXPORT_DIR="${EXPORT_DIR:-./hostpath_pod_backups}"
        mkdir -p "${EXPORT_DIR}"

        echo "=== Detecting Pods using hostPath volumes (excluding core system namespaces) ==="

        # Capture the raw audit output and a machine-parsable JSON list
        AUDIT_OUTPUT="$(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 // []) as $vols
          | [ $vols[] | select(.hostPath != null) ] as $hp
          | select(($hp | length) > 0)
          | {
              namespace: $m.namespace,
              name: $m.name,
              uid: $m.uid,
              creationTimestamp: ($m.creationTimestamp // ""),
              node: (.spec.nodeName // ""),
              labels: ($m.labels // {}),
              owner: ([($m.ownerReferences // [])[] | select(.controller)] | first),
              hostPaths: [ $hp[] | .hostPath.path ]
            }
          ]')"

        if [[ -z "${AUDIT_OUTPUT}" || "${AUDIT_OUTPUT}" == "[]" ]]; then
          echo "No non-system Pods with hostPath volumes detected. Cluster is compliant."
          exit 0
        fi

        echo "Non-compliant Pods detected:"
        echo "${AUDIT_OUTPUT}" | jq -r '.[] | "- ns=\(.namespace) pod=\(.name) hostPaths=\(.hostPaths | join("+")) ownerKind=\(.owner.kind // "NONE") ownerName=\(.owner.name // "NONE")"'

        # Export manifests for manual editing
        echo
        echo "=== Exporting non-compliant Pod manifests for manual remediation ==="
        echo "Manifests will be saved under: ${EXPORT_DIR}"

        echo "${AUDIT_OUTPUT}" | jq -c '.[]' | while read -r item; do
          ns="$(echo "${item}" | jq -r '.namespace')"
          pod="$(echo "${item}" | jq -r '.name')"
          owner_kind="$(echo "${item}" | jq -r '.owner.kind // ""')"
          owner_name="$(echo "${item}" | jq -r '.owner.name // ""')"

          if [[ -n "${owner_kind}" && -n "${owner_name}" ]]; then
            # We export the owning controller manifest, not the bare Pod,
            # because changes must be made at the controller spec.
            # Supported common kinds: Deployment, StatefulSet, DaemonSet, ReplicaSet, Job, CronJob.
            # We rely on kubectl to pluralize correctly via 'get <kind>.<group>'.
            export_name="${owner_kind,,}-${owner_name}-ns-${ns}.yaml"
            if ! kubectl -n "${ns}" get "${owner_kind,,}/${owner_name}" >/dev/null 2>&1; then
              echo "  [WARN] Owner ${owner_kind}/${owner_name} in ns ${ns} not directly retrievable; exporting Pod instead."
              export_name="pod-${pod}-ns-${ns}.yaml"
              kubectl -n "${ns}" get pod "${pod}" -o yaml > "${EXPORT_DIR}/${export_name}"
            else
              echo "  Exporting ${owner_kind}/${owner_name} in ns ${ns} to ${EXPORT_DIR}/${export_name}"
              kubectl -n "${ns}" get "${owner_kind,,}/${owner_name}" -o yaml > "${EXPORT_DIR}/${export_name}"
            fi
          else
            export_name="pod-${pod}-ns-${ns}.yaml"
            echo "  Exporting stand-alone Pod ${pod} in ns ${ns} to ${EXPORT_DIR}/${export_name}"
            kubectl -n "${ns}" get pod "${pod}" -o yaml > "${EXPORT_DIR}/${export_name}"
          fi
        done

        cat <<'EOF'

        NEXT STEPS (manual per benchmark guidance):
          1. For each exported manifest under ./hostpath_pod_backups:
               - Locate any volumes with type `hostPath`.
               - Remove those `hostPath` volumes and all references under `volumeMounts`.
               - Replace them with:
                   * PersistentVolumeClaim-backed volumes when you need persistent storage, or
                   * `emptyDir` volumes for ephemeral storage, or
                   * Projected/configMap/secret volumes where appropriate.
          2. Apply your updated manifests back to the cluster with:
               kubectl apply -f <edited-manifest>.yaml

        This script can optionally delete existing non-compliant Pods (or their controllers)
        so that only your fixed versions run afterwards.
        EOF

        if [[ "${DRY_RUN}" == "true" ]]; then
          echo
          echo "DRY_RUN is true; no deletions will be performed."
          echo "Set DRY_RUN=false to enable interactive deletion of offending resources after you apply fixed manifests."
        else
          echo
          read -r -p "DRY_RUN=false: proceed to delete non-compliant Pods/controllers? [y/N]: " ans
          if [[ "${ans}" =~ ^[Yy]$ ]]; then
            echo
            echo "=== Deleting non-compliant Pods or their controllers ==="
            echo "${AUDIT_OUTPUT}" | jq -c '.[]' | while read -r item; do
              ns="$(echo "${item}" | jq -r '.namespace')"
              pod="$(echo "${item}" | jq -r '.name')"
              owner_kind="$(echo "${item}" | jq -r '.owner.kind // ""')"
              owner_name="$(echo "${item}" | jq -r '.owner.name // ""')"

              if [[ -n "${owner_kind}" && -n "${owner_name}" ]]; then
                # Delete controller; Kubernetes will recreate Pods from your updated manifest
                if kubectl -n "${ns}" get "${owner_kind,,}/${owner_name}" >/dev/null 2>&1; then
                  echo "  Deleting ${owner_kind}/${owner_name} in ns ${ns}"
                  kubectl -n "${ns}" delete "${owner_kind,,}/${owner_name}"
                else
                  echo "  [WARN] Owner ${owner_kind}/${owner_name} not found; deleting Pod ${pod} in ns ${ns} instead."
                  kubectl -n "${ns}" delete pod "${pod}"
                fi
              else
                echo "  Deleting stand-alone Pod ${pod} in ns ${ns}"
                kubectl -n "${ns}" delete pod "${pod}"
              fi
            done
          else
            echo "Skipping deletions at user request."
          fi
        fi

        echo
        echo "=== Verification: re-running audit to confirm compliance ==="
        VERIFY_OUTPUT="$(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
          | select(($hp | length) > 0)
          ] | length')"

        if [[ "${VERIFY_OUTPUT}" == "0" ]]; then
          echo "Verification passed: no non-system Pods with hostPath volumes remain."
          exit 0
        else
          echo "Verification FAILED: ${VERIFY_OUTPUT} non-system Pods with hostPath volumes still present."
          echo "Re-run the export, update the manifests to remove hostPath, apply them, and run this script again."
          exit 1
        fi
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
