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. Identify non-compliant pods (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' \
    | grep 'is_compliant=false'
  2. For each non-compliant pod, determine its owner (run on any machine with kubectl access):

    kubectl get pod POD_NAME -n POD_NAMESPACE -o jsonpath='{.metadata.ownerReferences[*].kind}{" "}{.metadata.ownerReferences[*].name}{"\n"}'
    • If there is no ownerReference, the Pod is standalone and must be fixed directly.
    • If there is an owner (Deployment, DaemonSet, StatefulSet, Job, etc.), you must edit that owner resource instead of the Pod.
  3. Edit the owning workload (preferred) to remove host namespace sharing (run on any machine with kubectl access):

    kubectl -n POD_NAMESPACE edit DEPLOYMENT_OR_DAEMONSET_NAME

    In the opened manifest:

    • Locate .spec.template.spec.hostPID, .spec.template.spec.hostIPC, and .spec.template.spec.hostNetwork.
    • Set each present field to false:
      spec:
      template:
      spec:
      hostPID: false
      hostIPC: false
      hostNetwork: false
      or delete the lines entirely so they are omitted. Save and exit; Kubernetes will recreate pods from this workload. Be aware this may restart application pods and briefly disrupt traffic.
  4. If the Pod is standalone (no controller), edit or recreate it from a manifest (run on any machine with kubectl access):

    • If you have a manifest in version control, update it so the pod spec does not set these fields (omit them or set to false):
      spec:
      hostPID: false
      hostIPC: false
      hostNetwork: false
      Then:
      kubectl apply -f PATH_TO_UPDATED_MANIFEST.yaml
    • If there is no manifest, export, edit, and re-create:
      kubectl get pod POD_NAME -n POD_NAMESPACE -o yaml > /tmp/pod-fixed.yaml
      sed -i '/hostPID:/d;/hostIPC:/d;/hostNetwork:/d' /tmp/pod-fixed.yaml
      # remove runtime-only fields
      sed -i '/^ resourceVersion:/d;/^ uid:/d;/^ creationTimestamp:/d;/^ selfLink:/d;/^ managedFields:/,/^[^ ]/d' /tmp/pod-fixed.yaml
      sed -i '/status:/,$d' /tmp/pod-fixed.yaml
      kubectl delete pod POD_NAME -n POD_NAMESPACE
      kubectl apply -f /tmp/pod-fixed.yaml
      This will delete and recreate the pod; expect a restart and potential brief downtime.
  5. Repeat steps 2–4 for all pods reported as is_compliant=false, ensuring all updated specs have hostPID, hostIPC, and hostNetwork either omitted or explicitly set to false.

  6. Verify remediation (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'

    Confirm the output is is_compliant=true and that no lines show is_compliant=false.

Using kubectl

On any machine with kubectl access:

  1. Identify offending pods and their controllers (exclude system namespaces 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
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| (.spec.hostPID // false) as $hostPID
| (.spec.hostIPC // false) as $hostIPC
| (.spec.hostNetwork // false) as $hostNet
| select($hostPID or $hostIPC or $hostNet)
| "\($m.namespace) \($m.name) \((if $own == null then "Pod" else $own.kind end))"
][]'
  1. For each owning controller (Deployment, DaemonSet, StatefulSet, Job, CronJob, or bare Pod), export its manifest:
# Example for a Deployment
kubectl get deployment <deployment-name> -n <namespace> -o yaml > /tmp/<deployment-name>.yaml
  1. Edit the manifest to remove or explicitly set the host namespace fields under spec.template.spec (or spec for a bare Pod):
spec:
template:
spec:
# Remove these if present, or set them to false:
hostPID: false
hostIPC: false
hostNetwork: false

For bare Pods (not managed by a controller):

spec:
# Remove these if present, or set them to false:
hostPID: false
hostIPC: false
hostNetwork: false
  1. Apply the updated manifest:
kubectl apply -f /tmp/<deployment-name>.yaml

If you had to delete and recreate a bare Pod:

kubectl delete pod <pod-name> -n <namespace>
kubectl apply -f /tmp/<pod-name>.yaml
  1. Verification (re-run the audit and confirm all non-system pods are is_compliant=true):
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
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ " 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: Ensure pods do not share host namespaces (hostPID/hostIPC/hostNetwork)
# Scope: Any machine with kubectl access to the OKE cluster
#
# Behavior:
# - Scans all namespaces except kube-system, kube-public, kube-node-lease
# - For pods with hostPID/hostIPC/hostNetwork == true:
# * If controlled by a workload (Deployment/DaemonSet/StatefulSet/ReplicaSet/Job/CronJob):
# - Patch the controller’s pod template to set those fields to false
# - Delete the non-compliant pods so the controller recreates them
# * If not controlled by a workload:
# - Patches the pod in place to set those fields to false
# - Safe to re-run: patches are idempotent; deleting managed pods lets controllers reconcile
# - Verifies compliance at the end using the authoritative audit command

set -euo pipefail

# REQUIREMENTS:
# - kubectl configured to point at the target OKE cluster
# - jq installed

# Helper: run kubectl with stable output (no colors, etc.)
k() {
kubectl "$@"
}

echo "Scanning for non-compliant pods (sharing host namespaces)..."

NON_COMPLIANT_JSON=$(k get pods --all-namespaces -o json \
| jq '
.items[]
| select(.metadata.namespace as $n
| ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| . as $pod
| (.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 "Non-compliant pods detected. Applying fixes..."

# Process each non-compliant pod
echo "${NON_COMPLIANT_JSON}" | jq -c '.' | while read -r POD_JSON; do
NS=$(jq -r '.metadata.namespace' <<< "${POD_JSON}")
NAME=$(jq -r '.metadata.name' <<< "${POD_JSON}")

HOSTPID=$(jq -r '(.spec.hostPID // false) | tostring' <<< "${POD_JSON}")
HOSTIPC=$(jq -r '(.spec.hostIPC // false) | tostring' <<< "${POD_JSON}")
HOSTNET=$(jq -r '(.spec.hostNetwork // false) | tostring' <<< "${POD_JSON}")

echo "Processing pod ${NS}/${NAME} (hostPID=${HOSTPID}, hostIPC=${HOSTIPC}, hostNetwork=${HOSTNET})"

# Detect controlling ownerReference (if any)
OWNER_KIND=$(jq -r '[.metadata.ownerReferences // [] | .[] | select(.controller)] | first.kind // ""' <<< "${POD_JSON}")
OWNER_NAME=$(jq -r '[.metadata.ownerReferences // [] | .[] | select(.controller)] | first.name // ""' <<< "${POD_JSON}")

# Patch payload to disable host namespaces
PATCH='{"spec":{"hostPID":false,"hostIPC":false,"hostNetwork":false}}'

if [[ -n "${OWNER_KIND}" && -n "${OWNER_NAME}" ]]; then
echo " -> Pod is controlled by ${OWNER_KIND}/${OWNER_NAME}. Patching controller template."

# Determine patch path depending on controller kind
case "${OWNER_KIND}" in
Deployment|StatefulSet|DaemonSet|ReplicaSet)
TEMPLATE_PATH="spec.template.spec"
;;
Job)
TEMPLATE_PATH="spec.template.spec"
;;
CronJob)
# For CronJob v1: spec.jobTemplate.spec.template.spec
TEMPLATE_PATH="spec.jobTemplate.spec.template.spec"
;;
*)
echo " !! Unsupported owner kind ${OWNER_KIND}. Skipping controller patch; patching pod directly."
TEMPLATE_PATH=""
;;
esac

if [[ -n "${TEMPLATE_PATH}" ]]; then
# Build a strategic merge patch targeting the pod template
CONTROLLER_PATCH=$(jq -n --arg path "${TEMPLATE_PATH}" '
($path | split(".") ) as $p
| {"spec":{}} as $obj
| reduce range(0; ($p|length)) as $i (
$obj;
if $i == 0 then
.[$p[$i]] //= {}
elif $i == ($p|length - 1) then
path($p) |= . + {"hostPID":false,"hostIPC":false,"hostNetwork":false}
else
path($p[0:$i+1]) //= {}
end
)
')

echo " -> Patching ${OWNER_KIND}/${NS}/${OWNER_NAME}..."
k -n "${NS}" patch "${OWNER_KIND,,}" "${OWNER_NAME}" --type=merge -p "${CONTROLLER_PATCH}" >/dev/null

echo " -> Deleting non-compliant pod so controller recreates it..."
k -n "${NS}" delete pod "${NAME}" --wait=false >/dev/null || true
else
echo " -> Patching pod ${NS}/${NAME} directly..."
k -n "${NS}" patch pod "${NAME}" --type=merge -p "${PATCH}" >/dev/null || true
fi
else
echo " -> Pod has no controlling owner. Patching pod directly."
k -n "${NS}" patch pod "${NAME}" --type=merge -p "${PATCH}" >/dev/null || true
fi
done
fi

echo "Verification: re-running compliance check..."

k 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 | map(select(. | contains("is_compliant=false"))) | length) == 0
then "is_compliant=true"
else $rows[]
end
'