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.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'
  2. 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>
  3. 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/namespace
    kubectl -n <NAMESPACE> edit deployment <DEPLOYMENT_NAME>

    In the editor, under spec.template.spec, ensure:

    hostPID: false # or remove the line entirely if present
    hostIPC: false # or remove the line entirely if present
    hostNetwork: false # or remove the line entirely if present

    Save and exit; Kubernetes will roll out updated pods.

  4. 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 spec
    kubectl -n <NAMESPACE> get pod <POD_NAME> -o yaml > /tmp/<POD_NAME>.yaml

    Edit /tmp/<POD_NAME>.yaml with 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: false
      hostIPC: false
      hostNetwork: false

    Apply the corrected manifest and delete the old pod:

    kubectl -n <NAMESPACE> delete pod <POD_NAME>
    kubectl apply -f /tmp/<POD_NAME>.yaml
  5. 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.

  6. 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=true or shows all remaining non-compliant pods as explicitly accepted exceptions.

Using kubectl

On any machine with kubectl access:

  1. 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)"
][]'
  1. 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
  1. 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
  1. 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