> ## 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. **Identify pods using `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' \
           | grep 'is_compliant=false'
           ```

        2. **Export the owning workload manifest(s)**\
           For each non‑compliant pod line, note the `owner=` field (e.g., `Deployment/ns/name/uid`). Then export that owner resource. Examples:\
           Run on: any machine with kubectl access
           ```bash theme={null}
           # Deployment
           kubectl -n <NAMESPACE> get deployment <NAME> -o yaml > /tmp/<NAMESPACE>-<NAME>-deploy.yaml

           # StatefulSet
           kubectl -n <NAMESPACE> get statefulset <NAME> -o yaml > /tmp/<NAMESPACE>-<NAME>-sts.yaml

           # DaemonSet
           kubectl -n <NAMESPACE> get daemonset <NAME> -o yaml > /tmp/<NAMESPACE>-<NAME>-ds.yaml

           # If pod has no controller, export the pod spec directly
           kubectl -n <NAMESPACE> get pod <POD_NAME> -o yaml > /tmp/<NAMESPACE>-<POD_NAME>-pod.yaml
           ```

        3. **Edit manifests to remove `hostPath` volumes and use safer alternatives**\
           Run on: any machine with kubectl access (local file edits)\
           Open each exported YAML file and in the `spec.template.spec.volumes` (or `spec.volumes` for standalone pods), remove entries that define `hostPath`. Example before/after:

           Before:

           ```yaml theme={null}
           volumes:
             - name: data
               hostPath:
                 path: /var/lib/myapp
                 type: DirectoryOrCreate
           ```

           Replace with an approved alternative, such as `emptyDir` or a `persistentVolumeClaim` (if appropriate for the application):

           `emptyDir` example:

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

           PVC example (requires an existing PVC or one you create separately):

           ```yaml theme={null}
           volumes:
             - name: data
               persistentVolumeClaim:
                 claimName: myapp-data-pvc
           ```

           Ensure matching `volumeMounts` refer to the updated volume name and that any dependency on the host filesystem is addressed functionally (e.g., by moving data to PV-backed storage).

        4. **Apply the updated manifests**\
           Run on: any machine with kubectl access

           ```bash theme={null}
           # Apply each modified file
           kubectl apply -f /tmp/<NAMESPACE>-<NAME>-deploy.yaml
           kubectl apply -f /tmp/<NAMESPACE>-<NAME>-sts.yaml
           kubectl apply -f /tmp/<NAMESPACE>-<NAME>-ds.yaml
           kubectl apply -f /tmp/<NAMESPACE>-<POD_NAME>-pod.yaml
           ```

           The workloads will be recreated/rolled out with the new volume configuration; expect pod restarts as part of this change.

        5. **Confirm there are no remaining `hostPath` volumes in non‑system namespaces**\
           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'
           ```

           Compliance is achieved when the output is either a single line `is_compliant=true` or no lines contain `is_compliant=false`.
      </Accordion>

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

        1. Identify noncompliant Pods (and their controllers)

        ```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 violating controller (Deployment, DaemonSet, StatefulSet, Job, etc.), edit the manifest to remove `hostPath` volumes and replace them with `emptyDir`, a PersistentVolumeClaim, or another non-hostPath type.

        Example: patch a Deployment to replace a `hostPath` with `emptyDir` (adjust names, namespace, volume names as needed):

        ```bash theme={null}
        kubectl -n default patch deployment example-app \
          --type='json' \
          -p='[
            {
              "op": "replace",
              "path": "/spec/template/spec/volumes/0",
              "value": {
                "name": "data",
                "emptyDir": {}
              }
            }
          ]'
        ```

        If you use manifests in Git or files, update them and apply declaratively:

        ```bash theme={null}
        kubectl apply -f /absolute/path/to/updated-manifest.yaml
        ```

        3. For standalone Pods (no controller ownerReference), recreate them without `hostPath`:

        a. Export, edit, and save as a manifest:

        ```bash theme={null}
        kubectl -n default get pod example-pod -o yaml > /absolute/path/to/example-pod.yaml
        ```

        Edit `/absolute/path/to/example-pod.yaml`:

        * Remove any `hostPath:` blocks under `spec.volumes`.
        * Adjust containers’ `volumeMounts` to use the new volume types.

        b. Delete and recreate the Pod:

        ```bash theme={null}
        kubectl -n default delete pod example-pod
        kubectl apply -f /absolute/path/to/example-pod.yaml
        ```

        4. Verification (same command as audit)

        ```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 CBP C1.7:
        # Remove hostPath volumes from Pods (excluding kube-system, kube-public, kube-node-lease).
        #
        # Runs on: any machine with kubectl and jq access to the cluster.
        # Idempotent: can be safely re-run; only touches workloads that use hostPath.

        set -euo pipefail

        # --- Preconditions ------------------------------------------------------------

        if ! command -v kubectl >/dev/null 2>&1; then
          echo "ERROR: kubectl not found in PATH." >&2
          exit 1
        fi

        if ! command_which_jq=$(command -v jq 2>/dev/null); then
          echo "ERROR: jq not found in PATH (required for audit)." >&2
          exit 1
        fi

        KUBECTL_CONTEXT="${KUBECTL_CONTEXT:-}"
        KUBECTL="kubectl"
        if [ -n "$KUBECTL_CONTEXT" ]; then
          KUBECTL="$KUBECTL --context=${KUBECTL_CONTEXT}"
        fi

        # --- Helper: identify controllers owning non-compliant Pods -------------------

        echo "Scanning for Pods that mount hostPath volumes (excluding system namespaces)..."

        NON_COMPLIANT_JSON="$($KUBECTL get pods --all-namespaces -o json | jq '
          .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | select([(.spec.volumes // [])[] | select(.hostPath != null)] | length > 0)
        ')"

        if [ -z "$NON_COMPLIANT_JSON" ]; then
          echo "No Pods with hostPath volumes found (outside excluded namespaces). Cluster is compliant."
          exit 0
        fi

        # Extract distinct owning controllers (kind, namespace, name, apiVersion)
        # We will *not* patch Pods directly; instead patch the owning workload manifests.
        CONTROLLERS_JSON="$(printf '%s\n' "$NON_COMPLIANT_JSON" | jq -r '
          .
          | .metadata as $m
          | [ ($m.ownerReferences // [])[] | select(.controller) ] | first as $own
          | if $own == null then empty else
              {
                kind:       $own.kind,
                apiVersion: $own.apiVersion,
                namespace:  $m.namespace,
                name:       $own.name
              }
            end
        ' | jq -s 'unique_by(.kind, .apiVersion, .namespace, .name)')"

        if [ "$(printf '%s\n' "$CONTROLLERS_JSON" | jq 'length')" -eq 0 ]; then
          echo "WARNING: Found Pods with hostPath but no controller ownerReferences."
          echo "These are likely bare Pods; they must be edited or recreated manually."
          echo "Listing affected Pods:"
          printf '%s\n' "$NON_COMPLIANT_JSON" | jq -r '.metadata.namespace + "/" + .metadata.name'
          echo
          echo "No automated fix applied. Review and replace these Pods without hostPath volumes."
        else
          echo "Found the following owning controllers to inspect and manually remediate:"
          printf '%s\n' "$CONTROLLERS_JSON" | jq -r '.[] | "\(.kind) \(.apiVersion) \(.namespace)/\(.name)"'
          echo
          cat <<'EOF'
        NOTE: Per the benchmark remediation, hostPath volumes must be removed and replaced
        with safer alternatives such as:
          - PersistentVolumes / PersistentVolumeClaims
          - emptyDir
          - projected / configMap / secret volumes

        Automatic, generic removal of hostPath from controller specs cannot be performed
        safely without application-specific knowledge (e.g., what data source to use in
        place of hostPath). You must update the manifests for the listed controllers
        (Deployments, StatefulSets, DaemonSets, Jobs, etc.) to remove hostPath and
        deploy a suitable alternative volume type.

        This script will now only verify current non-compliance; no in-place mutation
        is done to avoid breaking workloads.
        EOF
        fi

        # --- Verification (same logic as audit, for idempotent re-run) ----------------

        echo
        echo "Re-running compliance audit after manual remediation (if any was done)..."

        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.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 "$AUDIT_OUTPUT"

        # A simple compliance exit code: 0 if fully compliant, 1 otherwise
        if printf '%s\n' "$AUDIT_OUTPUT" | grep -q 'is_compliant=false'; then
          echo
          echo "Cluster is NOT fully compliant. Remaining non-compliant Pods are listed above."
          exit 1
        else
          echo
          echo "Cluster is compliant with CBP C1.7 (no hostPath volumes on Pods outside system namespaces)."
          exit 0
        fi
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
