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
Remediation​
Manual Steps
-
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)"][]' -
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.
-
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 Deploymentkubectl edit deployment NAME -n NAMESPACEIn 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 linehostIPC: false # or remove this linehostNetwork: false # or remove this lineSave and exit; Kubernetes will roll out updated pods.
-
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.yamlEdit
/tmp/pod-POD_NAME.yamland underspecremovehostPID,hostIPC, andhostNetworkor set them tofalse. Also removemetadata.resourceVersion,metadata.uid,metadata.creationTimestamp,metadata.managedFields,status, and anyownerReferences. Then recreate:kubectl delete pod POD_NAME -n NAMESPACEkubectl apply -f /tmp/pod-POD_NAME.yaml -
Repeat steps 2–4 for each non-compliant pod until all workloads have
hostPID,hostIPC, andhostNetworkomitted or set tofalsein their pod specs. -
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) == 0then "is_compliant=true"else $rows[]end'
Using kubectl
On any machine with kubectl access:
- 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'
- For each non-compliant pod, edit its controller manifest (Deployment, StatefulSet, DaemonSet, Job, etc.) to ensure
hostPID,hostIPC, andhostNetworkare 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.
- 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