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 a 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 non-compliant pod, identify and edit its owning workload (Deployment/DaemonSet/StatefulSet/Job/CronJob) in its namespace (run on any machine with kubectl access). Example for a Deployment:

    kubectl -n <namespace> get pod <pod-name> -o jsonpath='{.metadata.ownerReferences[0].kind}{" "}{.metadata.ownerReferences[0].name}{"\n"}'

    kubectl -n <namespace> edit <OwnerKind>/<OwnerName>

    In the opened manifest, locate .spec.template.spec and ensure:

    spec:
    # remove these lines if present, or set them to false
    hostPID: false
    hostIPC: false
    hostNetwork: false

    Save and exit to apply the change. Kubernetes will recreate pods with the updated spec.

  3. If a non-compliant pod is standalone (no ownerReferences), fetch its manifest, modify, and re-create it (run on any machine with kubectl access):

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

    Edit /tmp/pod-fixed.yaml and under spec: remove hostPID, hostIPC, hostNetwork fields or set them to false, and delete fields that must not be reused (metadata.resourceVersion, metadata.uid, metadata.creationTimestamp, metadata.managedFields, status). Then:

    kubectl -n <namespace> delete pod <pod-name>
    kubectl -n <namespace> apply -f /tmp/pod-fixed.yaml
  4. If the pod is managed by GitOps or other IaC, also update the source manifest so changes are not reverted (run in your IaC workflow environment). In the relevant YAML file, under the pod template:

    spec:
    # ensure these are omitted or explicitly set to false
    hostPID: false
    hostIPC: false
    hostNetwork: false

    Commit and push through your normal deployment pipeline.

  5. For workloads that genuinely require host namespaces (for example, certain node-level monitoring/diagnostics agents), perform a risk review and formally document the exception. Keep the settings only where strictly necessary and ensure those pods are constrained (e.g., dedicated namespace, RBAC, NetworkPolicies).

  6. Verify all non-system pods are compliant (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(. | contains("is_compliant=false"))) | length) == 0
    then "is_compliant=true"
    else $rows[]
    end'

    The cluster is compliant when the output is is_compliant=true and no rows show is_compliant=false.

Using kubectl

On any machine with kubectl access:

  1. Identify non-compliant pods (excluding AKS system namespaces):
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)"
][]'
  1. For each listed pod, edit its controller (Deployment, StatefulSet, DaemonSet, Job, CronJob) so the template does not set host namespaces. Example for a Deployment:
kubectl -n <namespace> edit deployment <deployment-name>

In the opened manifest, within spec.template.spec, ensure these fields are either removed or explicitly set to false:

spec:
template:
spec:
hostPID: false
hostIPC: false
hostNetwork: false

Save and exit; Kubernetes will roll out updated pods.

If the pod is a standalone Pod (no controller), replace it with a compliant manifest:

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

Edit /tmp/pod-fixed.yaml:

  • Remove status fields and metadata fields that block creation (resourceVersion, uid, managedFields, etc.).
  • Under spec, set or remove host namespace fields:
spec:
hostPID: false
hostIPC: false
hostNetwork: false

Delete and recreate:

kubectl -n <namespace> delete pod <pod-name>
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
| select($hostPID or $hostIPC or $hostNet)
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
#
# Remediate pods that share host namespaces (hostPID/hostIPC/hostNetwork)
# Target: any machine with kubectl access to the AKS cluster
#
# Requirements:
# - kubectl configured to talk to the cluster
# - jq installed
#
# This script:
# 1. Identifies non-system pods with hostPID/hostIPC/hostNetwork = true
# 2. Patches supported controllers (Deployment/StatefulSet/DaemonSet/ReplicaSet/Job/CronJob)
# to set these fields to false (or remove them).
# 3. Reports pods that cannot be auto-fixed (e.g. naked Pods, custom controllers).
# 4. Re-runs the audit at the end.

set -euo pipefail

echo "=== Detecting non-compliant pods (excluding kube-system, kube-public, kube-node-lease) ==="

NON_COMPLIANT_JSON="$(kubectl get pods --all-namespaces -o json | jq -c '
.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)
')"

if [[ -z "${NON_COMPLIANT_JSON}" ]]; then
echo "No non-compliant pods found. Cluster already compliant."
else
echo "Found non-compliant pods:"
echo "${NON_COMPLIANT_JSON}" | jq -r '.metadata.namespace + "/" + .metadata.name + " hostPID=" + ((.spec.hostPID // false)|tostring) + " hostIPC=" + ((.spec.hostIPC // false)|tostring) + " hostNetwork=" + ((.spec.hostNetwork // false)|tostring)'
fi

echo
echo "=== Identifying owning controllers for non-compliant pods ==="

# Helper function: create a JSON patch that unsets/removes host* fields
make_patch() {
jq -n '
{
"spec": {
"template": {
"spec": {}
}
}
}
| .spec.template.spec.hostPID = false
| .spec.template.spec.hostIPC = false
| .spec.template.spec.hostNetwork = false
'
}

# Store patch in a variable for reuse
PATCH_JSON="$(make_patch)"

CONTROLLERS_PATCHED=()
PODS_MANUAL_REVIEW=()

# Process each non-compliant pod and patch its owning controller if possible
if [[ -n "${NON_COMPLIANT_JSON}" ]]; then
while IFS= read -r pod; do
ns="$(jq -r '.metadata.namespace' <<<"${pod}")"
pod_name="$(jq -r '.metadata.name' <<<"${pod}")"

owner_kind="$(jq -r '([(.metadata.ownerReferences // [])[] | select(.controller)] | first | .kind) // ""' <<<"${pod}")"
owner_name="$(jq -r '([(.metadata.ownerReferences // [])[] | select(.controller)] | first | .name) // ""' <<<"${pod}")"

if [[ -z "${owner_kind}" || -z "${owner_name}" ]]; then
PODS_MANUAL_REVIEW+=("${ns}/${pod_name} (no controller owner; likely a naked Pod)")
continue
fi

# Map supported controller kinds to their resource types
case "${owner_kind}" in
Deployment|StatefulSet|DaemonSet|ReplicaSet|Job)
resource="${owner_kind,,}s" # lowercase + plural
api_version="apps/v1"
;;
CronJob)
resource="cronjobs"
api_version="batch/v1"
;;
*)
PODS_MANUAL_REVIEW+=("${ns}/${pod_name} (unsupported owner kind: ${owner_kind}; manual review)")
continue
;;
esac

controller_key="${ns}/${resource}/${owner_name}"

# Avoid patching the same controller multiple times
if printf '%s\n' "${CONTROLLERS_PATCHED[@]:-}" | grep -qx -- "${controller_key}"; then
continue
fi

echo
echo "Patching ${owner_kind} ${ns}/${owner_name} to disable hostPID/hostIPC/hostNetwork..."

# Apply JSON patch (strategic merge)
kubectl -n "${ns}" patch "${resource}.${api_version}" "${owner_name}" \
--type merge \
-p "${PATCH_JSON}"

CONTROLLERS_PATCHED+=("${controller_key}")
done <<<"${NON_COMPLIANT_JSON}"
fi

echo
echo "=== Summary of automated changes ==="
if [[ ${#CONTROLLERS_PATCHED[@]} -eq 0 ]]; then
echo "No controllers were patched (none found or all require manual review)."
else
printf 'Patched controllers:\n'
printf ' %s\n' "${CONTROLLERS_PATCHED[@]}"
fi

if [[ ${#PODS_MANUAL_REVIEW[@]} -gt 0 ]]; then
echo
echo "The following pods require manual review (e.g., standalone Pods, custom controllers):"
printf ' %s\n' "${PODS_MANUAL_REVIEW[@]}"
echo "For each, update the owning manifest so that spec.hostPID/spec.hostIPC/spec.hostNetwork are omitted or set to false."
fi

echo
echo "=== Waiting for pods to roll out after controller patches ==="
# Give the cluster a bit of time to recreate pods; this is best-effort.
sleep 10

echo
echo "=== Re-running compliance 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'