Skip to main content

Pods Should Not Share Host Namespaces

More Info:​

Verifies no pod sets hostPID, hostIPC or hostNetwork. Sharing a host namespace breaks the isolation boundary between the pod and the node.

Risk Level​

Critical

Address​

Security

Compliance Standards​

  • Cloudanix Best Practice

Triage and Remediation​

Remediation​

Manual Steps
  1. List all non-system pods that share any host namespace (run on any machine with kubectl access):

    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.hostPID // false) as $hostPID
    | (.spec.hostIPC // false) as $hostIPC
    | (.spec.hostNetwork // false) as $hostNet
    | select($hostPID or $hostIPC or $hostNet)
    | "ns=\($m.namespace) name=\($m.name) hostPID=\($hostPID) hostIPC=\($hostIPC) hostNetwork=\($hostNet)"
    ][]'
  2. For each offending pod, identify its controller (Deployment/DaemonSet/StatefulSet/Job/etc.) and namespace (run on any machine with kubectl access; replace NAMESPACE and POD_NAME):

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

    If empty, the pod is standalone and must be deleted and recreated from a corrected manifest.

  3. Edit the owning workload to remove host namespace sharing (run on any machine with kubectl access; choose the correct kind and set NAMESPACE/NAME):

    # Example for a Deployment
    kubectl edit deployment NAME -n NAMESPACE

    In the editor, under spec.template.spec, ensure these fields are either removed or explicitly set to false:

    spec:
    template:
    spec:
    hostPID: false # or remove this line
    hostIPC: false # or remove this line
    hostNetwork: false # or remove this line

    Save and exit; Kubernetes will roll out updated pods.

  4. For standalone pods not managed by a controller (run on any machine with kubectl access; replace NAMESPACE and POD_NAME):

    kubectl get pod POD_NAME -n NAMESPACE -o yaml > /tmp/pod-POD_NAME.yaml

    Edit /tmp/pod-POD_NAME.yaml and under spec remove hostPID, hostIPC, and hostNetwork or set them to false. Also remove metadata.resourceVersion, metadata.uid, metadata.creationTimestamp, metadata.managedFields, status, and any ownerReferences. Then recreate:

    kubectl delete pod POD_NAME -n NAMESPACE
    kubectl apply -f /tmp/pod-POD_NAME.yaml
  5. Repeat steps 2–4 for each non-compliant pod until all workloads have hostPID, hostIPC, and hostNetwork omitted or set to false in their pod specs.

  6. Verify no remaining non-system pods share host namespaces (run on any machine with kubectl access):

    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.hostPID // false) as $hostPID
    | (.spec.hostIPC // false) as $hostIPC
    | (.spec.hostNetwork // false) as $hostNet
    | "ns=\($m.namespace) name=\($m.name) hostPID=\($hostPID) hostIPC=\($hostIPC) hostNetwork=\($hostNet)
    is_compliant=\(if ($hostPID or $hostIPC or $hostNet) then "false" else "true" end)"
    ] as $rows
    | if ($rows | map(select(. | test("is_compliant=false"))) | length) == 0
    then "is_compliant=true"
    else $rows[]
    end'
Using kubectl

On any machine with kubectl access:

  1. Identify all pods using host namespaces (excluding core namespaces, as per the audit):
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.hostPID // false) as $hostPID
| (.spec.hostIPC // false) as $hostIPC
| (.spec.hostNetwork // false) as $hostNet
| "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)
+ " hostPID=\($hostPID) hostIPC=\($hostIPC) hostNetwork=\($hostNet)"
+ " is_compliant=\(if ($hostPID or $hostIPC or $hostNet) then "false" else "true" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
  1. For each non-compliant pod, edit its controller manifest (Deployment, StatefulSet, DaemonSet, Job, etc.) to ensure hostPID, hostIPC, and hostNetwork are not set to true.

Example: patch a Deployment to explicitly disable them:

kubectl patch deployment <deployment-name> \
-n <namespace> \
--type merge \
-p '{
"spec": {
"template": {
"spec": {
"hostPID": false,
"hostIPC": false,
"hostNetwork": false
}
}
}
}'

Example: if the running pod is not controlled by a higher-level object (standalone Pod), export, modify, and re-apply:

kubectl get pod <pod-name> -n <namespace> -o yaml > /tmp/pod-fixed.yaml

Edit /tmp/pod-fixed.yaml and in spec remove hostPID, hostIPC, and hostNetwork fields entirely, or set them to false:

spec:
# hostPID: true # REMOVE
# hostIPC: true # REMOVE
# hostNetwork: true # REMOVE
containers:
- name: ...
image: ...

Delete and recreate the pod from the fixed manifest:

kubectl delete pod <pod-name> -n <namespace>
kubectl apply -f /tmp/pod-fixed.yaml

Note: changing these fields causes pods to be recreated by their controllers, which may briefly disrupt workloads using host namespaces.

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

Re-run the audit command; it should now report is_compliant=true for all remaining rows or a single is_compliant=true line:

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.hostPID // false) as $hostPID
| (.spec.hostIPC // false) as $hostIPC
| (.spec.hostNetwork // false) as $hostNet
| "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)
+ " hostPID=\($hostPID) hostIPC=\($hostIPC) hostNetwork=\($hostNet)"
+ " is_compliant=\(if ($hostPID or $hostIPC or $hostNet) then "false" else "true" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
#
# Remediation for: Pods Should Not Share Host Namespaces (hostPID/hostIPC/hostNetwork)
# Scope: GKE cluster, all namespaces except kube-system, kube-public, kube-node-lease
# Requirements: kubectl, jq, yq (https://mikefarah.gitbook.io/yq/) installed on this machine
# Safe to re-run: yes (only patches pods that request host namespaces)

set -euo pipefail

# --- configuration ---

# Label key/value to mark non-compliant pods so controllers can be fixed by their owners.
MARK_LABEL_KEY="security.host-namespaces"
MARK_LABEL_VALUE="noncompliant"

# --- functions ---

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

verify_cluster_access() {
if ! kubectl version --request-timeout=10s >/dev/null 2>&1; then
echo "ERROR: cannot talk to cluster with kubectl. Check kubeconfig/context." >&2
exit 1
fi
}

# List non-compliant pods (excluding control-plane/system namespaces)
list_noncompliant_pods() {
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)
| select((.spec.hostPID // false) or (.spec.hostIPC // false) or (.spec.hostNetwork // false))
| "\(.metadata.namespace) \(.metadata.name)"
'
}

# Try to patch pod spec to disable host namespaces.
# Note: For pods managed by controllers, the controller will usually recreate them with the same spec.
# This script therefore ALSO tags such pods so owners can update the real manifests/IaC.
patch_pod() {
local ns="$1"
local name="$2"

echo "Processing pod ${ns}/${name} ..."

# Get ownerReferences to see if this is a bare pod
local owners
owners="$(kubectl -n "${ns}" get pod "${name}" -o json | jq '.metadata.ownerReferences // []')"

# Always label pod as noncompliant so it can be tracked
kubectl -n "${ns}" label pod "${name}" \
"${MARK_LABEL_KEY}=${MARK_LABEL_VALUE}" --overwrite >/dev/null

if [ "${owners}" != "[]" ]; then
echo " -> Pod is controlled by a higher-level resource; not editing controller via this script."
echo " Marked with label ${MARK_LABEL_KEY}=${MARK_LABEL_VALUE}."
return 0
fi

# For standalone pods, we can directly patch the pod spec and recreate it
echo " -> Pod has no controller; attempting safe reconciliation."

# Export full manifest
tmpdir="$(mktemp -d)"
manifest="${tmpdir}/pod.yaml"
new_manifest="${tmpdir}/pod-fixed.yaml"

kubectl -n "${ns}" get pod "${name}" -o yaml > "${manifest}"

# Remove fields that must not be set on create
# and set hostPID/hostIPC/hostNetwork to false (or omit them).
yq '
del(.metadata.uid,
.metadata.resourceVersion,
.metadata.selfLink,
.metadata.creationTimestamp,
.metadata.generation,
.metadata.managedFields,
.metadata.annotations."kubectl.kubernetes.io/last-applied-configuration",
.status)
| .spec.hostPID = false
| .spec.hostIPC = false
| .spec.hostNetwork = false
' "${manifest}" > "${new_manifest}"

echo " -> Deleting original pod ${ns}/${name} ..."
kubectl -n "${ns}" delete pod "${name}" --wait=true

echo " -> Recreating pod ${ns}/${name} without host namespaces ..."
kubectl -n "${ns}" apply -f "${new_manifest}"

rm -rf "${tmpdir}"
}

verify_compliance() {
echo
echo "=== Verification (should report is_compliant=true or only compliant pods) ==="
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.hostPID // false) as $hostPID
| (.spec.hostIPC // false) as $hostIPC
| (.spec.hostNetwork // false) as $hostNet
| "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)
+ " hostPID=\($hostPID) hostIPC=\($hostIPC) hostNetwork=\($hostNet)"
+ " is_compliant=\(if ($hostPID or $hostIPC or $hostNet) then "false" else "true" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
}

# --- main ---

need_bin kubectl
need_bin jq
need_bin yq
verify_cluster_access

echo "Discovering non-compliant pods (using hostPID/hostIPC/hostNetwork) ..."
mapfile -t pods < <(list_noncompliant_pods || true)

if [ "${#pods[@]}" -eq 0 ]; then
echo "No non-compliant pods found."
verify_compliance
exit 0
fi

echo "Found ${#pods[@]} non-compliant pod(s)."

for line in "${pods[@]}"; do
ns="$(echo "${line}" | awk '{print $1}')"
name="$(echo "${line}" | awk '{print $2}')"
patch_pod "${ns}" "${name}"
done

verify_compliance