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.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 reported pod, identify whether it is managed by a higher-level controller (Deployment, StatefulSet, DaemonSet, Job, etc.) by reviewing the
owner=field from the previous output, or by describing it (run on any machine with kubectl access, example namespace/name):kubectl -n <NAMESPACE> describe pod <POD_NAME> -
If the pod is managed by a controller (preferred approach), edit the controller’s manifest to remove host namespace sharing (run on any machine with kubectl access):
# Example for a Deployment; replace with the actual kind/name/namespacekubectl -n <NAMESPACE> edit deployment <DEPLOYMENT_NAME>In the editor, under
spec.template.spec, ensure:hostPID: false # or remove the line entirely if presenthostIPC: false # or remove the line entirely if presenthostNetwork: false # or remove the line entirely if presentSave and exit; Kubernetes will roll out updated pods.
-
If the pod is standalone (no controller), delete and recreate it from a corrected manifest (run on any machine with kubectl access):
# Export the existing pod speckubectl -n <NAMESPACE> get pod <POD_NAME> -o yaml > /tmp/<POD_NAME>.yamlEdit
/tmp/<POD_NAME>.yamlwith a text editor:- Remove
metadata.resourceVersion,metadata.uid,metadata.managedFields,metadata.creationTimestamp,metadata.ownerReferences,status, and other runtime-only fields. - Under
spec, remove or set:hostPID: falsehostIPC: falsehostNetwork: false
Apply the corrected manifest and delete the old pod:
kubectl -n <NAMESPACE> delete pod <POD_NAME>kubectl apply -f /tmp/<POD_NAME>.yaml - Remove
-
For workloads that legitimately require host namespaces (e.g., certain monitoring/telemetry agents), document the justification and ensure they are limited to the minimal namespaces needed and to trusted namespaces/nodes. Consider using dedicated node pools and labels/taints to isolate such pods.
-
Verify that no non-system pod shares 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.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'Confirm the output is either
is_compliant=trueor shows all remaining non-compliant pods as explicitly accepted exceptions.
Using kubectl
On any machine with kubectl access:
- Identify non-compliant pods (for context)
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)"
][]'
- Edit or patch workload manifests (Deployments, DaemonSets, etc.) to remove or disable host namespaces.
Example manifest snippet (desired state – ensure all three are false or omitted):
apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: default
spec:
template:
spec:
# Remove these lines entirely, or set them to false
# hostPID: false
# hostIPC: false
# hostNetwork: false
containers:
- name: app
image: your-image
Apply updated manifest:
kubectl apply -f path/to/your-workload.yaml
- If you must adjust a naked Pod (not controlled by a higher-level object), recreate it with compliant spec:
a. Export current spec:
kubectl get pod POD_NAME -n NAMESPACE -o yaml > /tmp/pod-fixed.yaml
b. Edit /tmp/pod-fixed.yaml: remove status: section and remove or set to false these fields under spec:
spec:
hostPID: false # or delete line
hostIPC: false # or delete line
hostNetwork: false # or delete line
c. Delete and recreate the pod:
kubectl delete pod POD_NAME -n NAMESPACE
kubectl apply -f /tmp/pod-fixed.yaml
- Verification (same machine with kubectl):
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)"
] as $rows
| if ($rows | map(select(. | test("hostPID=true|hostIPC=true|hostNetwork=true"))) | length) == 0
then "is_compliant=true"
else $rows[]
end'
Automation
#!/usr/bin/env bash
#
# Remediation: Ensure pods do not share host namespaces (hostPID/hostIPC/hostNetwork)
# Scope: Any machine with kubectl access to the cluster
# Requirements: kubectl, jq
#
# Behavior:
# - Finds all non-system pods with hostPID/hostIPC/hostNetwork == true
# - Patches their parent controller (Deployment/DaemonSet/StatefulSet/ReplicaSet/Job/CronJob)
# to set those fields to false (or add them as false if absent)
# - If a pod has no controller (standalone Pod), it is reported and must be fixed manually
# - Safe to re-run: patches are idempotent
# - Verifies at the end using the benchmark audit logic
set -euo pipefail
# -------------------------
# Helper Functions
# -------------------------
require_bin() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "ERROR: required command '$1' not found in PATH" >&2
exit 1
fi
}
json_patch_spec_security() {
# Emit a jsonpatch fragment to ensure spec.template.spec.{hostPID,hostIPC,hostNetwork} are false
cat <<'EOF'
[
{
"op": "add",
"path": "/spec/template/spec/hostPID",
"value": false
},
{
"op": "add",
"path": "/spec/template/spec/hostIPC",
"value": false
},
{
"op": "add",
"path": "/spec/template/spec/hostNetwork",
"value": false
}
]
EOF
}
json_patch_cronjob() {
# Emit a jsonpatch fragment to ensure spec.jobTemplate.spec.template.spec.{hostPID,hostIPC,hostNetwork} are false
cat <<'EOF'
[
{
"op": "add",
"path": "/spec/jobTemplate/spec/template/spec/hostPID",
"value": false
},
{
"op": "add",
"path": "/spec/jobTemplate/spec/template/spec/hostIPC",
"value": false
},
{
"op": "add",
"path": "/spec/jobTemplate/spec/template/spec/hostNetwork",
"value": false
}
]
EOF
}
# -------------------------
# Preconditions
# -------------------------
require_bin kubectl
require_bin jq
echo "Discovering pods that share host namespaces (excluding kube-system, kube-public, kube-node-lease)..."
pods_json="$(kubectl get pods --all-namespaces -o json)"
# -------------------------
# Identify non-compliant pods
# -------------------------
non_compliant_pods=$(echo "${pods_json}" | jq -r '
.items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| (.spec.hostPID // false) as $hostPID
| (.spec.hostIPC // false) as $hostIPC
| (.spec.hostNetwork // false) as $hostNet
| select($hostPID or $hostIPC or $hostNet)
| {
ns: .metadata.namespace,
name: .metadata.name,
hostPID: $hostPID,
hostIPC: $hostIPC,
hostNet: $hostNet,
owner: ([ (.metadata.ownerReferences // [])[] | select(.controller) ] | first)
}
| @base64
')
if [ -z "${non_compliant_pods}" ]; then
echo "No non-compliant pods found; cluster already conforms to the requirement."
exit 0
fi
echo "Found non-compliant pods. Processing controllers..."
# Track standalone pods (no owner) for manual review
standalone_report=""
# -------------------------
# Patch controllers
# -------------------------
while IFS= read -r row; do
[ -z "$row" ] && continue
pod=$(echo "$row" | base64 --decode)
ns=$(echo "$pod" | jq -r '.ns')
name=$(echo "$pod" | jq -r '.name')
hostPID=$(echo "$pod" | jq -r '.hostPID')
hostIPC=$(echo "$pod" | jq -r '.hostIPC')
hostNet=$(echo "$pod" | jq -r '.hostNet')
owner_kind=$(echo "$pod" | jq -r '.owner.kind // empty')
owner_name=$(echo "$pod" | jq -r '.owner.name // empty')
if [ -z "${owner_kind}" ] || [ -z "${owner_name}" ]; then
standalone_report+=$'\n'"- Pod ${ns}/${name} (hostPID=${hostPID}, hostIPC=${hostIPC}, hostNetwork=${hostNet}) has no controller; fix its manifest or recreate it with host* fields omitted or set to false."
continue
fi
echo "Patching controller ${owner_kind}/${ns}/${owner_name} for pod ${ns}/${name} ..."
case "${owner_kind}" in
Deployment|DaemonSet|StatefulSet|ReplicaSet|Job)
patch_payload="$(json_patch_spec_security)"
kubectl -n "${ns}" patch "${owner_kind,,}/${owner_name}" --type=json -p "${patch_payload}" >/dev/null
;;
CronJob)
patch_payload="$(json_patch_cronjob)"
kubectl -n "${ns}" patch cronjob "${owner_name}" --type=json -p "${patch_payload}" >/dev/null
;;
*)
echo "WARNING: Unsupported owner kind '${owner_kind}' for pod ${ns}/${name}; skipping automatic fix."
standalone_report+=$'\n'"- Pod ${ns}/${name} owned by unsupported kind ${owner_kind}; review and set hostPID/hostIPC/hostNetwork to false in its spec."
;;
esac
done <<< "${non_compliant_pods}"
if [ -n "${standalone_report}" ]; then
echo
echo "Manual review required for the following pods/controllers:"
echo "${standalone_report}"
echo
fi
# -------------------------
# Verification
# -------------------------
echo "Waiting briefly for controllers to roll out updated pods..."
sleep 10
echo "Verifying that no non-system pod shares a host namespace..."
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)
| (.spec.hostPID // false) as $hostPID
| (.spec.hostIPC // false) as $hostIPC
| (.spec.hostNetwork // false) as $hostNet
| select($hostPID or $hostIPC or $hostNet)
] | length')
if [ "${verify_output}" -eq 0 ]; then
echo "Verification passed: no non-system pods currently share hostPID, hostIPC, or hostNetwork."
exit 0
else
echo "WARNING: Verification detected ${verify_output} non-compliant pod(s) still sharing host namespaces."
echo "Inspect them with:"
echo " kubectl get pods --all-namespaces -o wide | grep -vE 'kube-system|kube-public|kube-node-lease'"
exit 1
fi