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
Remediation
Manual Steps
-
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_namespacedopod_hostpid=$(kubectl get pod "${pod_name}" --namespace "${pod_namespace}" -o jsonpath='{.spec.hostPID}' 2>/dev/null)if [ "${pod_hostpid}" = "true" ]; thenecho "NON-COMPLIANT: pod_name=${pod_name} pod_namespace=${pod_namespace} hostPID=${pod_hostpid}"fidone -
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
hostPIDtruly 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
hostPIDfrom the workload spec or replace it with a safer pattern.
- Is
-
Remove
hostPID: truefrom workloads that do not require it (any machine with kubectl access)
For each non-exempt workload, edit the owning controller and removehostPID: truefromspec.template.spec:# Example: Deploymentkubectl -n <NAMESPACE> edit deployment <DEPLOYMENT_NAME># Example: DaemonSetkubectl -n <NAMESPACE> edit daemonset <DAEMONSET_NAME>In the editor, delete the line:
hostPID: trueSave 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 -
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 disallowhostPID(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 \--overwriteThen explicitly document exceptions (e.g., a dedicated namespace with a less restrictive policy) and ensure they are tightly controlled.
-
Optionally create a dedicated namespace for justified
hostPIDworkloads (any machine with kubectl access)
If you must allow some hostPID workloads, isolate them:kubectl create namespace hostpid-exceptionsApply 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.
-
Verification (any machine with kubectl access)
Re-run the audit to confirm no unintendedhostPID: truepods 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_namespacedopod_hostpid=$(kubectl get pod "${pod_name}" --namespace "${pod_namespace}" -o jsonpath='{.spec.hostPID}' 2>/dev/null)if [ -z "${pod_hostpid}" ]; thenpod_hostpid="false"echo "***pod_name: ${pod_name} pod_namespace: ${pod_namespace} is_pod_hostpid: ${pod_hostpid} is_compliant: true"elseecho "***pod_name: ${pod_name} pod_namespace: ${pod_namespace} is_pod_hostpid: ${pod_hostpid} is_compliant: false"fidoneConfirm that only explicitly approved exception workloads (if any) show
is_pod_hostpid: trueand 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."