> ## 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 non-system pods using hostPath and identify owners (Deployments, DaemonSets, 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
             | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
             | [ (.spec.volumes // [])[] | select(.hostPath != null) ] as $hp
             | select(($hp | length) > 0)
             | "\($m.namespace) \($m.name) \($own.kind)//\($own.name)//\($own.uid)" ][]'
           ```

        2. For each affected pod, get the full manifest of its controller (example for a Deployment; run on any machine with kubectl access):
           ```bash theme={null}
           # Replace NAMESPACE and DEPLOYMENT_NAME from step 1 output
           kubectl -n NAMESPACE get deploy DEPLOYMENT_NAME -o yaml > /tmp/DEPLOYMENT_NAME.yaml
           ```
           For other controllers, change `deploy` to `statefulset`, `daemonset`, `job`, or `cronjob` as appropriate.

        3. Edit the saved manifest to remove `hostPath` volumes and references (any machine with kubectl access):
           * Open the file:
             ```bash theme={null}
             vi /tmp/DEPLOYMENT_NAME.yaml
             ```
           * Under `spec.template.spec.volumes`, delete any entries containing `hostPath:`, including the `path` field.
           * Under `spec.template.spec.containers[].volumeMounts`, delete mounts that reference the removed volume names.
           * Optionally add safer replacements such as `emptyDir: {}` or PVC-backed volumes according to your application’s storage requirements.

        4. Apply the updated manifest (any machine with kubectl access):
           ```bash theme={null}
           kubectl apply -f /tmp/DEPLOYMENT_NAME.yaml
           ```
           Confirm old pods are being replaced and new pods are running:
           ```bash theme={null}
           kubectl -n NAMESPACE get pods -l app=APP_LABEL
           ```

        5. For any stand-alone Pod objects (no controller ownerReference), edit them in place to remove `hostPath` (any machine with kubectl access):
           ```bash theme={null}
           kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/POD_NAME.yaml
           vi /tmp/POD_NAME.yaml
           # Remove hostPath entries and related volumeMounts as in step 3
           kubectl delete pod -n NAMESPACE POD_NAME
           kubectl apply -f /tmp/POD_NAME.yaml
           ```

        6. Verify no non-system pods use hostPath (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[] end'
           ```
           Compliance is achieved when the output is exactly:\
           `is_compliant=true`
      </Accordion>

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

        1. Identify the offending pod and workload

        ```bash theme={null}
        kubectl get pods --all-namespaces \
          -o wide
        ```

        Find the namespace and pod reported in the finding, then see what owns it:

        ```bash theme={null}
        kubectl -n NAMESPACE get pod POD_NAME -o jsonpath='{.metadata.ownerReferences}'
        ```

        * If `ownerReferences` is empty: it’s a naked Pod; you will edit/recreate the Pod.
        * If it shows a controller (`Deployment`, `StatefulSet`, `DaemonSet`, etc.): you will edit that controller, not the pod.

        2. Export the current spec

        For a naked Pod (no controller):

        ```bash theme={null}
        kubectl -n NAMESPACE get pod POD_NAME -o yaml > pod-fixed.yaml
        ```

        For a controller (example: Deployment; adjust kind/name as needed):

        ```bash theme={null}
        kubectl -n NAMESPACE get deployment DEPLOYMENT_NAME -o yaml > deploy-fixed.yaml
        ```

        3. Edit the manifest to remove `hostPath`

        Open the saved YAML file and in the pod template:

        * Under `spec.volumes`, delete any entries that contain `hostPath:`.
        * Under each container’s `volumeMounts`, delete mounts that refer to those deleted volumes.

        Example of what to remove:

        ```yaml theme={null}
        spec:
          volumes:
            - name: host-logs
              hostPath:
                path: /var/log
                type: Directory
        ```

        And its mount:

        ```yaml theme={null}
        containers:
          - name: app
            volumeMounts:
              - name: host-logs
                mountPath: /var/log/host
        ```

        If storage is still needed, replace with one of:

        * `emptyDir`:

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

        * A PersistentVolumeClaim (assuming it already exists):

          ```yaml theme={null}
          volumes:
            - name: app-data
              persistentVolumeClaim:
                claimName: EXISTING_PVC_NAME
          ```

        Then mount `app-data` instead of the `hostPath` volume in `volumeMounts`.

        4. Apply the updated manifest

        For a naked Pod, delete and recreate (Pods cannot be updated in place):

        ```bash theme={null}
        kubectl -n NAMESPACE delete pod POD_NAME
        kubectl apply -f pod-fixed.yaml
        ```

        For a controller (example: Deployment):

        ```bash theme={null}
        kubectl apply -f deploy-fixed.yaml
        ```

        Kubernetes will roll out new pods without the `hostPath` volumes.

        5. Verification

        On any machine with kubectl access, rerun:

        ```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 for: Pods Should Not Mount hostPath Volumes (CBP C1.7)
        # Scope: Any machine with kubectl access to the OKE cluster
        #
        # Strategy:
        # - Find all non-system Pods that use hostPath volumes
        # - For Pods that are part of a controller (Deployment/DaemonSet/StatefulSet/Job/CronJob/ReplicaSet/ReplicationController),
        #   remove hostPath volumes from the controller spec.
        # - For naked Pods, warn and optionally delete (cannot safely auto-recreate).
        # - Verify that no remaining Pods use hostPath volumes.
        #
        # Requirements:
        # - kubectl configured with cluster-admin (or equivalent) access
        # - jq available in PATH
        # - bash 4+

        set -euo pipefail

        # --------- Helpers ---------

        need_bin() {
          if ! command -v "$1" >/dev/null 2>&1; then
            echo "ERROR: Required binary '$1' not found in PATH" >&2
            exit 1
          fi
        }

        need_bin kubectl
        need_bin jq

        KUBECTL_CONTEXT="${KUBECTL_CONTEXT:-}"

        kc() {
          if [[ -n "${KUBECTL_CONTEXT}" ]]; then
            kubectl --context "${KUBECTL_CONTEXT}" "$@"
          else
            kubectl "$@"
          fi
        }

        echo "Using kubectl context:"
        kc config current-context

        # Ensure we can talk to the cluster
        kc get ns >/dev/null

        # Namespaces excluded from the check (per audit command)
        EXCLUDED_NS_REGEX='^(kube-system|kube-public|kube-node-lease)$'

        timestamp() {
          date -u +"%Y%m%dT%H%M%SZ"
        }

        backup_dir="./hostpath-controller-backups-$(timestamp)"
        mkdir -p "${backup_dir}"

        # --------- Discovery ---------

        echo "Discovering Pods with hostPath volumes (excluding kube-system, kube-public, kube-node-lease)..."

        # Get detailed JSON for pods with hostPath and not in excluded namespaces
        PODS_JSON="$(kc get pods --all-namespaces -o json)"

        AFFECTED_PODS_JSON="$(
          echo "${PODS_JSON}" | jq '
            .items[]
            | select(.metadata.namespace | test("'"${EXCLUDED_NS_REGEX}"'") | not)
            | (.spec.volumes // []) as $vols
            | select([ $vols[]? | select(.hostPath != null) ] | length > 0)
          '
        )"

        if [[ -z "${AFFECTED_PODS_JSON}" ]]; then
          echo "No pods with hostPath volumes found outside excluded namespaces."
          exit 0
        fi

        # --------- Process each affected Pod ---------

        echo "Processing affected pods..."

        # We iterate pod by pod
        echo "${AFFECTED_PODS_JSON}" | jq -c '.' | while read -r POD; do
          NS="$(echo "${POD}" | jq -r '.metadata.namespace')"
          NAME="$(echo "${POD}" | jq -r '.metadata.name')"

          # Extract controller ownerReference (if any)
          OWNER_JSON="$(echo "${POD}" | jq -c '[.metadata.ownerReferences // [] | .[] | select(.controller == true)] | first // empty')"

          if [[ -z "${OWNER_JSON}" || "${OWNER_JSON}" == "null" ]]; then
            echo "WARN: Pod ${NS}/${NAME} is a naked Pod using hostPath. Automation will NOT modify naked Pods."
            echo "      Consider: kubectl delete pod -n ${NS} ${NAME} after creating a replacement without hostPath."
            continue
          fi

          OWNER_KIND="$(echo "${OWNER_JSON}" | jq -r '.kind')"
          OWNER_NAME="$(echo "${OWNER_JSON}" | jq -r '.name')"
          OWNER_UID="$(echo "${OWNER_JSON}" | jq -r '.uid')"

          echo "Found controller-managed Pod: ${NS}/${NAME} owned by ${OWNER_KIND}/${OWNER_NAME}"

          # Map ReplicaSet/ReplicationController back to top-level controller when possible
          TARGET_KIND="${OWNER_KIND}"
          TARGET_NAME="${OWNER_NAME}"
          TARGET_NS="${NS}"

          case "${OWNER_KIND}" in
            ReplicaSet)
              # Attempt to find owning Deployment
              RS_JSON="$(kc -n "${NS}" get replicaset "${OWNER_NAME}" -o json || true)"
              if [[ -n "${RS_JSON}" ]]; then
                DEPLOY_OWNER="$(echo "${RS_JSON}" | jq -c '[.metadata.ownerReferences // [] | .[] | select(.controller == true and .kind == "Deployment")] | first // empty')"
                if [[ -n "${DEPLOY_OWNER}" && "${DEPLOY_OWNER}" != "null" ]]; then
                  TARGET_KIND="Deployment"
                  TARGET_NAME="$(echo "${DEPLOY_OWNER}" | jq -r '.name')"
                fi
              fi
              ;;
            Job)
              # Attempt to find owning CronJob
              JOB_JSON="$(kc -n "${NS}" get job "${OWNER_NAME}" -o json || true)"
              if [[ -n "${JOB_JSON}" ]]; then
                CJ_OWNER="$(echo "${JOB_JSON}" | jq -c '[.metadata.ownerReferences // [] | .[] | select(.controller == true and .kind == "CronJob")] | first // empty')"
                if [[ -n "${CJ_OWNER}" && "${CJ_OWNER}" != "null" ]]; then
                  TARGET_KIND="CronJob"
                  TARGET_NAME="$(echo "${CJ_OWNER}" | jq -r '.name')"
                fi
              fi
              ;;
            *)
              # DaemonSet, StatefulSet, Deployment, CronJob, ReplicationController, etc.
              :
              ;;
          esac

          echo "  Target controller to patch: ${TARGET_KIND}/${TARGET_NS}/${TARGET_NAME}"

          # Fetch controller manifest
          CONTROLLER_FILE="${backup_dir}/${TARGET_NS}_${TARGET_KIND}_${TARGET_NAME}.yaml"

          if [[ ! -f "${CONTROLLER_FILE}" ]]; then
            echo "  Backing up existing controller manifest to ${CONTROLLER_FILE}"
            if ! kc -n "${TARGET_NS}" get "${TARGET_KIND,,}.${TARGET_NS}/${TARGET_NAME}" >/dev/null 2>&1; then
              # Fallback to kubectl get KIND NAME form (most common)
              kc -n "${TARGET_NS}" get "${TARGET_KIND}" "${TARGET_NAME}" -o yaml > "${CONTROLLER_FILE}"
            else
              kc get "${TARGET_KIND,,}.${TARGET_NS}/${TARGET_NAME}" -o yaml > "${CONTROLLER_FILE}"
            fi
          else
            echo "  Backup already exists: ${CONTROLLER_FILE}"
          fi

          # Generate patched manifest with hostPath removed from pod template
          PATCHED_FILE="${backup_dir}/${TARGET_NS}_${TARGET_KIND}_${TARGET_NAME}_patched.yaml"

          echo "  Generating patched manifest (removing hostPath volumes from pod template)..."

          # Use jq to strip hostPath volumes and volumeMounts referencing them from pod template
          # NOTE: This is best-effort; manual review of ${PATCHED_FILE} is strongly recommended.
          kc -n "${TARGET_NS}" get "${TARGET_KIND}" "${TARGET_NAME}" -o json | jq '
            # work with .spec.template.spec
            ( .spec.template.spec ) as $spec
            | (
                $spec.volumes // []
                | map(select(.hostPath == null))
              ) as $cleanVolumes
            | $spec.containers as $containers
            | $containers as $origContainers
            | $origContainers
              | map(
                  . as $c
                  | .volumeMounts = (
                      ($c.volumeMounts // [])
                      | map(select(.name as $vn | ($cleanVolumes | map(.name) | index($vn)) != null))
                    )
                ) as $cleanContainers
            | $spec.initContainers as $initContainers
            | $initContainers as $origInitContainers
            | $origInitContainers
              | map(
                  . as $c
                  | .volumeMounts = (
                      ($c.volumeMounts // [])
                      | map(select(.name as $vn | ($cleanVolumes | map(.name) | index($vn)) != null))
                    )
                ) as $cleanInitContainers
            | .spec.template.spec.volumes = $cleanVolumes
            | if ($cleanContainers | length) > 0 then
                .spec.template.spec.containers = $cleanContainers
              else
                .
              end
            | if ($cleanInitContainers | length) > 0 then
                .spec.template.spec.initContainers = $cleanInitContainers
              else
                .
              end
          ' > "${PATCHED_FILE}"

          echo "  Applying patched manifest for ${TARGET_KIND}/${TARGET_NS}/${TARGET_NAME}..."
          kc -n "${TARGET_NS}" apply -f "${PATCHED_FILE}"

          echo "  Controller patched. Pods will be recreated without hostPath volumes (if possible)."

        done

        # --------- Verification ---------

        echo "Waiting 10 seconds for controllers to reconcile..."
        sleep 10

        echo "Verifying that no remaining pods use hostPath volumes (excluding kube-system, kube-public, kube-node-lease)..."

        VERIFY_OUTPUT="$(
          kc 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'
        )"

        echo "${VERIFY_OUTPUT}"

        # If any non-compliant line remains, exit non-zero
        if echo "${VERIFY_OUTPUT}" | grep -q "is_compliant=false"; then
          echo "ERROR: Some pods still use hostPath volumes. Manual review required."
          echo "       Inspect the above list and adjust controller specs or naked pods accordingly."
          exit 2
        fi

        echo "Remediation succeeded: all checked pods are compliant (no hostPath volumes)."
        exit 0
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
