Skip to main content

Minimize Admission Containers Wishing Host Process ID

More Info:​

Do not generally permit containers to be run with the hostPID flag set to true.

Risk Level​

High

Address​

Security

Compliance Standards​

  • CIS Kubernetes

Triage and Remediation​

Remediation​

Manual Steps
  1. Identify pods using hostPID: true (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 [ "${pod_hostpid}" = "true" ]; then
    echo "NON-COMPLIANT: pod_name=${pod_name} pod_namespace=${pod_namespace} hostPID=${pod_hostpid}"
    fi
    done
  2. Review and decide which workloads are allowed to use hostPID (any machine with kubectl access)
    For each non-compliant pod from step 1, inspect its purpose and manifests (Deployments, DaemonSets, etc.) and decide:

    • Is hostPID truly required (e.g., low-level node monitoring / debugging)?
    • If yes, explicitly document the exception (namespace, workload name, business owner, justification).
    • If no, plan to remove hostPID from the workload spec or replace it with a safer pattern.
  3. Remove hostPID: true from workloads that do not require it (any machine with kubectl access)
    For each non-exempt workload, edit the owning controller and remove hostPID: true from spec.template.spec:

    # Example: Deployment
    kubectl -n <NAMESPACE> edit deployment <DEPLOYMENT_NAME>

    # Example: DaemonSet
    kubectl -n <NAMESPACE> edit daemonset <DAEMONSET_NAME>

    In the editor, delete the line:

    hostPID: true

    Save and exit to trigger a rollout without hostPID.
    For workloads managed via GitOps/IaC, make the same edit in the source manifests and re-apply:

    kubectl apply -f <PATH_TO_CLEANED_MANIFEST>.yaml
  4. Define a restrictive policy for each user-workload namespace (PodSecurityPolicy alternative: Pod Security Admission / admission controller) (any machine with kubectl access)
    Since this control is MANUAL, you must choose and implement a policy mechanism appropriate for your cluster (e.g., built-in Pod Security Admission, Kyverno, OPA/Gatekeeper). Example using built-in Pod Security Admission labels to disallow hostPID (applies if your cluster supports Pod Security Admission):

    kubectl label namespace <NAMESPACE> \
    pod-security.kubernetes.io/enforce=restricted \
    pod-security.kubernetes.io/enforce-version=latest \
    --overwrite

    Then explicitly document exceptions (e.g., a dedicated namespace with a less restrictive policy) and ensure they are tightly controlled.

  5. Optionally create a dedicated namespace for justified hostPID workloads (any machine with kubectl access)
    If you must allow some hostPID workloads, isolate them:

    kubectl create namespace hostpid-exceptions

    Apply a clearly documented, less restrictive policy only to this namespace (mechanism depends on your chosen admission controller) and ensure RBAC limits who can deploy there.

  6. Verification (any machine with kubectl access)
    Re-run the audit to confirm no unintended hostPID: true pods remain:

    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 only explicitly approved exception workloads (if any) show is_pod_hostpid: true and are documented as such.

Using kubectl
# 1) Identify namespaces with user workloads
# Run on: any machine with kubectl access
kubectl get ns

Create a baseline PodSecurity Baseline / restricted combo that disallows hostPID everywhere (except where you explicitly override it later):

# 2) Apply cluster-wide default: baseline+restricted (no hostPID)
# Run on: any machine with kubectl access
# Save as: podsecurity-defaults.yaml
apiVersion: v1
kind: Namespace
metadata:
name: default
---
apiVersion: v1
kind: Namespace
metadata:
name: workloads
---
apiVersion: v1
kind: Namespace
metadata:
name: apps
---
apiVersion: v1
kind: Namespace
metadata:
name: staging
---
apiVersion: v1
kind: Namespace
metadata:
name: prod
---
apiVersion: v1
kind: Namespace
metadata:
name: dev
---
apiVersion: v1
kind: Namespace
metadata:
name: qa
---
apiVersion: v1
kind: Namespace
metadata:
name: ci
---
apiVersion: v1
kind: Namespace
metadata:
name: cd
---
apiVersion: v1
kind: Namespace
metadata:
name: test
---
# Example of labels to enforce Pod Security admission:
# Adjust the list of Namespace objects above to match your real user-workload namespaces
# and keep *only* the ones you actually have. Then apply labels with this patch command instead

In practice you do not recreate namespaces; you label existing ones. For each namespace that should NOT allow hostPID, run:

# 3) Enforce baseline+restricted Pod Security in user-workload namespaces
# Run on: any machine with kubectl access

# Example for namespace "apps" – repeat for each user-workload namespace
kubectl label ns apps \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest \
--overwrite

For the rare namespace that must allow hostPID (e.g. system diagnostics), you consciously opt out by not setting these labels or by setting a lower level and documenting the exception:

# 4) Example: mark "ops-tools" as allowing hostPID (documented exception)
kubectl label ns ops-tools \
pod-security.kubernetes.io/enforce=privileged \
pod-security.kubernetes.io/enforce-version=latest \
--overwrite

If you do not use PodSecurity admission (older clusters), you must instead use a policy engine such as Gatekeeper/OPA or Kyverno. Example Kyverno policy (disallow hostPID except in explicitly allowed namespaces):

# 5) Kyverno ClusterPolicy to block hostPID pods except in allowed namespaces
# Run on: any machine with kubectl access
# Save as: disallow-hostpid.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-hostpid
spec:
validationFailureAction: Enforce
background: true
rules:
- name: block-hostpid
match:
any:
- resources:
kinds:
- Pod
namespaces:
- "*"
exclude:
any:
- resources:
namespaces:
- kube-system
- ops-tools # add any exception namespaces here explicitly
validate:
message: "Use of hostPID is restricted; remove spec.hostPID or use an approved namespace."
pattern:
spec:
hostPID: "false"

Apply the policy:

kubectl apply -f disallow-hostpid.yaml

Verification (matches the audit logic):

# 6) Verify there are no pods running with hostPID=true
# Run on: 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
Automation
#!/usr/bin/env bash
#
# Purpose:
# Minimize admission of containers with hostPID=true by applying a
# namespace-level restriction (Pod Security Admission or PSP fallback).
#
# Requirements:
# - Run on any machine with kubectl access and current-context pointing to the target cluster.
# - kubectl v1.25+ recommended (PSA annotations). For older clusters with PodSecurityPolicy
# still enabled, this script also creates a restrictive PSP + RBAC as a fallback.
#
# Behavior:
# - Skips system namespaces.
# - For each remaining namespace, applies:
# * Pod Security Admission annotations enforcing baseline level.
# (baseline implicitly disallows hostPID=true).
# - Creates a baseline PSP + RBAC only if the API exists and not already present.
# - Re-runs safely (idempotent).
# - Prints verification of pods with hostPID=true at the end.

set -euo pipefail

# -----------------------------
# Configuration
# -----------------------------

# Namespaces to skip (system/control-plane)
SKIP_NAMESPACES=(
"kube-system"
"kube-public"
"kube-node-lease"
"default" # remove from this list if you run user workloads in 'default'
)

PSP_NAME="restrict-hostpid-psp"
PSP_ROLE_NAME="restrict-hostpid-psp-role"
PSP_ROLEBINDING_NAME="restrict-hostpid-psp-rb"

# -----------------------------
# Helpers
# -----------------------------

ns_in_skip_list() {
local ns="$1"
for s in "${SKIP_NAMESPACES[@]}"; do
if [[ "$ns" == "$s" ]]; then
return 0
fi
done
return 1
}

api_exists() {
local api="$1"
if kubectl api-resources --api-group "$(cut -d'/' -f1 <<<"$api")" --no-headers 2>/dev/null | grep -q "$(cut -d'/' -f2 <<<"$api")"; then
return 0
fi
return 1
}

# -----------------------------
# Pod Security Admission (preferred)
# -----------------------------

apply_psa_annotations() {
echo "Applying Pod Security Admission annotations (baseline) to non-system namespaces..."

# Check if PSA is supported (Kubernetes >= 1.23 typically)
if ! kubectl api-resources | grep -q "podsecuritypolicies.authorization.k8s.io"; then
# Even if PSP API isn't present, PSA annotations are just labels/annotations and do not
# require an API resource. We'll always apply annotations; enforcement depends on the cluster config.
:
fi

# Annotate each non-system namespace
while IFS= read -r ns; do
ns_in_skip_list "$ns" && continue

echo " - Namespace: $ns"

# Patch namespace with PSA labels/annotations (idempotent).
# baseline disallows hostPID; enforce strict for workloads.
kubectl label namespace "$ns" \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest \
--overwrite

kubectl annotate namespace "$ns" \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/audit=baseline \
pod-security.kubernetes.io/warn-version=latest \
pod-security.kubernetes.io/audit-version=latest \
--overwrite

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

# -----------------------------
# Optional fallback: PodSecurityPolicy (legacy clusters only)
# -----------------------------

ensure_restrictive_psp() {
# Only if the PSP API exists
if ! kubectl api-versions 2>/dev/null | grep -q "^policy/v1beta1$"; then
echo "PodSecurityPolicy API not present; skipping PSP fallback."
return 0
fi

echo "PodSecurityPolicy API detected; ensuring restrictive PSP for hostPID..."

# Create PSP if it doesn't exist
if ! kubectl get psp "$PSP_NAME" >/dev/null 2>&1; then
cat <<EOF | kubectl apply -f -
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: ${PSP_NAME}
spec:
privileged: false
hostPID: false
hostNetwork: false
hostIPC: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
supplementalGroups:
rule: 'RunAsAny'
EOF
else
echo " PSP ${PSP_NAME} already exists; leaving as-is."
fi

# Create ClusterRole allowing use of the PSP
if ! kubectl get clusterrole "$PSP_ROLE_NAME" >/dev/null 2>&1; then
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ${PSP_ROLE_NAME}
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['${PSP_NAME}']
EOF
else
echo " ClusterRole ${PSP_ROLE_NAME} already exists; leaving as-is."
fi

# Bind the ClusterRole to all authenticated users (typical baseline)
if ! kubectl get clusterrolebinding "$PSP_ROLEBINDING_NAME" >/dev/null 2>&1; then
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ${PSP_ROLEBINDING_NAME}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ${PSP_ROLE_NAME}
subjects:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:authenticated
EOF
else
echo " ClusterRoleBinding ${PSP_ROLEBINDING_NAME} already exists; leaving as-is."
fi
}

# -----------------------------
# Main
# -----------------------------

echo "Starting remediation for hostPID usage..."

apply_psa_annotations
ensure_restrictive_psp

# -----------------------------
# Verification (from benchmark audit)
# -----------------------------

echo
echo "Verification: scanning all pods for hostPID=true ..."
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 [ -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

echo
echo "NOTE:"
echo "- Existing pods with hostPID=true will still show as non-compliant until recreated."
echo "- New pods in annotated namespaces should be prevented from using hostPID=true, subject to your cluster's admission configuration."

Additional Reading:​