Skip to main content

Minimize The Admission Of Containers Sharing The Host

More Info:

Sharing the host PID namespace lets a container see and interact with all host processes, aiding escape and escalation. Restrict hostPID pods.

Risk Level

High

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. List and review current Pods using hostPID (any machine with kubectl access)

    kubectl get pods --all-namespaces -o jsonpath='{range .items[?(@.spec.hostPID==true)]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}'

    For each listed Pod, decide if host PID access is strictly required. If not, plan to remove it; if yes, note the namespace so you can design exception policies (e.g., via labels).

  2. Create a restrictive PodSecurityPolicy or Pod Security Standards-style control (if PSP still in use) (any machine with kubectl access)
    If you still use PodSecurityPolicy, create one that forbids hostPID:

    cat << 'EOF' | kubectl apply -f -
    apiVersion: policy/v1beta1
    kind: PodSecurityPolicy
    metadata:
    name: restricted-no-hostpid
    spec:
    hostPID: false
    privileged: false
    runAsUser:
    rule: 'MustRunAsNonRoot'
    seLinux:
    rule: 'RunAsAny'
    fsGroup:
    rule: 'RunAsAny'
    supplementalGroups:
    rule: 'RunAsAny'
    volumes:
    - 'configMap'
    - 'emptyDir'
    - 'projected'
    - 'secret'
    - 'downwardAPI'
    - 'persistentVolumeClaim'
    EOF

    If your cluster instead uses Pod Security Admission (PSA), apply a namespace-level policy that disallows host namespaces. For example, enforce restricted on a namespace:

    kubectl label namespace my-workload-namespace \
    pod-security.kubernetes.io/enforce=restricted \
    --overwrite

    Repeat for each user-workload namespace, adjusting names as needed.

  3. Bind the policy to user-workload namespaces (if using PSP) (any machine with kubectl access)

    cat << 'EOF' | kubectl apply -f -
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
    name: use-restricted-no-hostpid-psp
    rules:
    - apiGroups: ['policy']
    resources: ['podsecuritypolicies']
    verbs: ['use']
    resourceNames: ['restricted-no-hostpid']
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
    name: use-restricted-no-hostpid-psp
    namespace: my-workload-namespace
    subjects:
    - kind: Group
    name: system:serviceaccounts:my-workload-namespace
    apiGroup: rbac.authorization.k8s.io
    roleRef:
    kind: ClusterRole
    name: use-restricted-no-hostpid-psp
    apiGroup: rbac.authorization.k8s.io
    EOF

    Repeat the RoleBinding stanza for each user-workload namespace by changing namespace: and the system:serviceaccounts:<namespace> group.

  4. Tighten admission using a validating admission policy (if PSA/PSP are not applicable) (any machine with kubectl access)
    If you rely on a generic admission controller (e.g., ValidatingAdmissionPolicy in newer Kubernetes), create a policy that rejects hostPID Pods, then selectively exempt namespaces that truly require it:

    cat << 'EOF' | kubectl apply -f -
    apiVersion: admissionregistration.k8s.io/v1
    kind: ValidatingWebhookConfiguration
    metadata:
    name: deny-hostpid-pods
    webhooks:
    - name: deny-hostpid-pods.example.com
    admissionReviewVersions: ["v1"]
    sideEffects: None
    failurePolicy: Fail
    rules:
    - apiGroups: [""]
    apiVersions: ["v1"]
    operations: ["CREATE","UPDATE"]
    resources: ["pods"]
    namespaceSelector:
    matchExpressions:
    - key: allow-hostpid
    operator: NotIn
    values: ["true"]
    clientConfig:
    # POINT THIS TO YOUR ACTUAL WEBHOOK SERVICE IMPLEMENTATION
    service:
    namespace: policy-system
    name: hostpid-deny-webhook
    path: /validate
    EOF

    Implement the webhook to reject any Pod with .spec.hostPID == true. Label only the namespaces that genuinely require hostPID with allow-hostpid=true to exempt them.

  5. Update or recreate non-essential hostPID Pods without hostPID (any machine with kubectl access)
    For any Pod/Deployment/DaemonSet/Job where hostPID is not strictly required, edit the manifest to remove or set hostPID: false, then apply:

    kubectl -n <namespace> get deployment <name> -o yaml > /tmp/deploy-no-hostpid.yaml
    sed -i 's/hostPID: true/hostPID: false/' /tmp/deploy-no-hostpid.yaml
    kubectl apply -f /tmp/deploy-no-hostpid.yaml

    For standalone Pods, delete and recreate them from updated manifests without hostPID: true.

  6. Verify no non-exempt Pods use hostPID (any machine with kubectl access)

    kubectl get pods --all-namespaces -o custom-columns=POD_NAME:.metadata.name,POD_NAMESPACE:.metadata.namespace --no-headers | while read -r pod_name pod_namespace
    do
    pod_hostpid=$(kubectl get pod "${pod_name}" --namespace "${pod_namespace}" -o jsonpath='{.spec.hostPID}' 2>/dev/null)
    if [ -z "${pod_hostpid}" ]; then
    pod_hostpid="false"
    echo "***pod_name: ${pod_name} pod_namespace: ${pod_namespace} is_pod_hostpid: ${pod_hostpid} is_compliant: true"
    else
    echo "***pod_name: ${pod_name} pod_namespace: ${pod_namespace} is_pod_hostpid: ${pod_hostpid} is_compliant: false"
    fi
    done

    Confirm that any is_compliant: false Pods exist only in namespaces you have explicitly designated as allowed to use hostPID and that they are strictly necessary.

Using kubectl
# 1) Create a baseline restrictive PodSecurityPolicy (if your cluster still uses PSP)
# This denies hostPID by default.
# Run on: any machine with kubectl access.

cat <<'EOF' > psp-deny-hostpid.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: deny-hostpid
spec:
privileged: false
hostPID: false
hostIPC: false
hostNetwork: false
seLinux:
rule: RunAsAny
runAsUser:
rule: RunAsAny
fsGroup:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
volumes:
- '*'
EOF

kubectl apply -f psp-deny-hostpid.yaml
# 2) Bind this PSP to a namespace with user workloads (example: "production").
# Replace "production" with each target namespace.
# Run on: any machine with kubectl access.

NAMESPACE=production

cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: use-deny-hostpid-psp
namespace: ${NAMESPACE}
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['deny-hostpid']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: use-deny-hostpid-psp
namespace: ${NAMESPACE}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: use-deny-hostpid-psp
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:serviceaccounts:${NAMESPACE}
EOF
# 3) (Alternative / additional) Use Pod Security Admission via labels to restrict hostPID.
# Use "restricted" or stricter for all user namespaces (example: "production").
# Run on: any machine with kubectl access.

kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
--overwrite
# 4) (Optional) Example: secure Pod manifest WITHOUT hostPID for user workloads.
# Ensure all application manifests in user namespaces omit hostPID or set it to false.

cat <<'EOF' > example-app-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: example-app
namespace: production
spec:
hostPID: false
containers:
- name: app
image: nginx:stable
EOF

kubectl apply -f example-app-pod.yaml
# 5) Verification: list all Pods with hostPID=true to confirm none exist in user namespaces.
# Run on: any machine with kubectl access.

kubectl get pods --all-namespaces -o json \
| jq -r '.items[]
| select(.spec.hostPID == true)
| "\(.metadata.namespace) \(.metadata.name) hostPID=true"' || true
Automation
#!/usr/bin/env bash
set -euo pipefail

# This script:
# - Creates a Pod Security Policy–style restriction using Pod Security Admission (PSA) via labels/Policy/PSP replacement
# - For each namespace with user workloads, it:
# * Labels the namespace with a restricted Pod Security level (baseline or restricted)
# * Creates/patches a validating admission policy (if supported) or a validating webhook
#
# NOTE:
# - This is a MANUAL benchmark control: different clusters use different admission stacks.
# - This script implements a conservative, Kubernetes-native control using Pod Security Admission labels
# and a validating admission policy if available. Review carefully before using in production.

# -------- CONFIGURATION --------

# Namespaces to ignore (system and infrastructure)
EXCLUDED_NAMESPACES=(
"kube-system"
"kube-public"
"kube-node-lease"
"local-path-storage"
"default" # remove this if you run user workloads in 'default'
)

# Pod Security level to enforce; choose "baseline" or "restricted"
POD_SECURITY_LEVEL="baseline"

# Label key for Pod Security Admission
POD_SECURITY_ENFORCE_LABEL="pod-security.kubernetes.io/enforce"

# ValidatingAdmissionPolicy name (for clusters >=1.26 supporting it)
VAP_NAME="deny-hostpid-pods"
VAP_BINDING_NAME="deny-hostpid-pods-binding"

# -------- FUNCTIONS --------

is_excluded_ns() {
local ns="$1"
for e in "${EXCLUDED_NAMESPACES[@]}"; do
if [[ "$ns" == "$e" ]]; then
return 0
fi
done
return 1
}

kubectl_safe() {
kubectl "$@" 2>/dev/null
}

# -------- PRECHECKS --------

echo "Checking kubectl access..."
kubectl version --short >/dev/null

echo "Determining cluster support for ValidatingAdmissionPolicy..."
if kubectl_safe api-resources | grep -q "^validatingadmissionpolicies"; then
HAS_VAP="true"
else
HAS_VAP="false"
fi
echo "ValidatingAdmissionPolicy supported: ${HAS_VAP}"

# -------- STEP 1: Label namespaces with Pod Security Admission --------
# Run on: any machine with kubectl access

echo "Labeling namespaces with Pod Security Admission level: ${POD_SECURITY_LEVEL}"

ALL_NAMESPACES=$(kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')

while read -r NS; do
[[ -z "$NS" ]] && continue
if is_excluded_ns "$NS"; then
echo "Skipping excluded namespace: ${NS}"
continue
fi

# Skip namespaces without user pods
POD_COUNT=$(kubectl get pods -n "$NS" --no-headers 2>/dev/null | wc -l | tr -d ' ')
if [[ "$POD_COUNT" -eq 0 ]]; then
echo "Skipping namespace with no pods: ${NS}"
continue
fi

CURRENT_LABEL=$(kubectl get ns "$NS" -o jsonpath="{.metadata.labels.${POD_SECURITY_ENFORCE_LABEL}}" 2>/dev/null || echo "")

if [[ "$CURRENT_LABEL" == "${POD_SECURITY_LEVEL}" ]]; then
echo "Namespace ${NS} already labeled ${POD_SECURITY_ENFORCE_LABEL}=${POD_SECURITY_LEVEL}"
else
echo "Labeling namespace ${NS} with ${POD_SECURITY_ENFORCE_LABEL}=${POD_SECURITY_LEVEL}"
kubectl label ns "$NS" "${POD_SECURITY_ENFORCE_LABEL}=${POD_SECURITY_LEVEL}" --overwrite
fi
done <<< "${ALL_NAMESPACES}"

# -------- STEP 2: Create/Update ValidatingAdmissionPolicy (if supported) --------
# Run on: any machine with kubectl access

if [[ "${HAS_VAP}" == "true" ]]; then
echo "Applying ValidatingAdmissionPolicy to deny hostPID pods..."

cat <<'EOF' | kubectl apply -f -
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: deny-hostpid-pods
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "!(has(object.spec.hostPID) && object.spec.hostPID == true)"
message: "Creating pods with hostPID=true is not allowed by policy"
reason: "Forbidden"
EOF

cat <<EOF | kubectl apply -f -
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: ${VAP_BINDING_NAME}
spec:
policyName: ${VAP_NAME}
matchResources:
namespaceSelector: {}
validationActions: ["Deny"]
EOF

else
echo "Cluster does not support ValidatingAdmissionPolicy;"
echo "you must manually implement an alternative admission control (e.g. Gatekeeper/PSP replacement)."
fi

# -------- STEP 3: Verification --------
# Run on: any machine with kubectl access

echo
echo "Verifying that existing pods do not use hostPID=true..."

NONCOMPLIANT=false
kubectl get pods --all-namespaces -o custom-columns=POD_NAME:.metadata.name,POD_NAMESPACE:.metadata.namespace --no-headers | \
while read -r pod_name pod_namespace; do
pod_hostpid=$(kubectl get pod "${pod_name}" --namespace "${pod_namespace}" -o jsonpath='{.spec.hostPID}' 2>/dev/null || true)
if [[ -n "${pod_hostpid}" && "${pod_hostpid}" == "true" ]]; then
echo "***pod_name: ${pod_name} pod_namespace: ${pod_namespace} is_pod_hostpid: ${pod_hostpid} is_compliant: false"
NONCOMPLIANT=true
fi
done

if [[ "${NONCOMPLIANT}" == "false" ]]; then
echo "All existing pods are compliant (hostPID not set or false)."
else
echo "Some existing pods are non-compliant; they must be manually reviewed and recreated without hostPID=true."
fi

echo
echo "To test enforcement, try creating a pod with spec.hostPID=true in a labeled user namespace; it should be denied."