Skip to main content

Minimize The Admission Of Containers Sharing The Host

More Info:​

Containers sharing the host PID namespace can view and interact with all processes on the node. Restrict admission of hostPID containers via namespace policies.

Risk Level​

High

Address​

Security

Compliance Standards​

  • CIS EKS

Triage and Remediation​

Remediation​

Manual Steps
  1. List all namespaces that contain user workloads (exclude system namespaces)
    Run on: any machine with kubectl access

    kubectl get ns

    Decide which namespaces should be protected (e.g., everything except kube-system, kube-public, kube-node-lease, etc.).

  2. For each target namespace, create or update a Pod Security admission label set to restricted (which forbids hostPID: true)
    Run on: any machine with kubectl access
    Replace <NAMESPACE> with the actual namespace name:

    kubectl label namespace <NAMESPACE> \
    pod-security.kubernetes.io/enforce=restricted \
    pod-security.kubernetes.io/enforce-version=latest \
    --overwrite
  3. Optionally add warnings/audit labels (recommended to catch violations before enforcement)
    Run on: any machine with kubectl access

    kubectl label namespace <NAMESPACE> \
    pod-security.kubernetes.io/warn=restricted \
    pod-security.kubernetes.io/warn-version=latest \
    pod-security.kubernetes.io/audit=restricted \
    pod-security.kubernetes.io/audit-version=latest \
    --overwrite
  4. For clusters not using Pod Security Admission and instead using an existing Validating/MutatingWebhookPolicy controller (e.g., OPA Gatekeeper, Kyverno), create a policy in that system denying spec.hostPID: true in the target namespaces.
    Run on: any machine with kubectl access
    Example Kyverno policy manifest (save as deny-hostpid.yaml and apply):

    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
    name: disallow-hostpid
    spec:
    validationFailureAction: enforce
    rules:
    - name: deny-hostpid
    match:
    any:
    - resources:
    kinds:
    - Pod
    validate:
    message: "Sharing the host PID namespace (hostPID) is not allowed."
    pattern:
    spec:
    hostPID: "false"

    Apply it:

    kubectl apply -f deny-hostpid.yaml
  5. Review and update existing manifests or Helm charts for user workloads to remove hostPID: true where it is not strictly required.

    • Search manifests locally:
      grep -Rni "hostPID" .
    • For any Pod/Deployment/DaemonSet/etc. manifest that contains hostPID: true, edit the file and set:
      spec:
      hostPID: false

    Then re-apply:

    kubectl apply -f <UPDATED_MANIFEST_FILE>
  6. Verification (no pods with hostPID: true should exist)
    Run on: any machine with kubectl access

    kubectl get pods --all-namespaces -o json | \
    jq -r 'if any(.items[]?; .spec.hostPID == true) then "HOSTPID_FOUND" else "NO_HOSTPID" end'

    Confirm the output is:

    NO_HOSTPID
Using kubectl
# 1) Create a baseline Pod Security Admission configuration that disallows hostPID
# Run on: any machine with kubectl access

cat << 'EOF' > psa-hostpid-restrict.yaml
apiVersion: policy/v1
kind: PodSecurityPolicy
metadata:
name: restricted-no-hostpid
labels:
app.kubernetes.io/name: restricted-no-hostpid
spec:
privileged: false
hostPID: false
hostIPC: false
hostNetwork: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
supplementalGroups:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
EOF

# Apply the PodSecurityPolicy (only works if PSP admission is enabled in your cluster)
kubectl apply -f psa-hostpid-restrict.yaml
# 2) Bind the PSP to each namespace with user workloads
# Replace <NAMESPACE> with each user-workload namespace name.
# Run on: any machine with kubectl access

NAMESPACE=<NAMESPACE>

kubectl create role psp-restricted-no-hostpid \
--verb=use \
--resource=podsecuritypolicies \
--resource-name=restricted-no-hostpid \
-n "$NAMESPACE"

kubectl create rolebinding psp-restricted-no-hostpid-binding \
--role=psp-restricted-no-hostpid \
--group=system:serviceaccounts:"$NAMESPACE" \
-n "$NAMESPACE"
# 3) (Alternative / in addition) Enforce Pod Security Admission labels to restrict hostPID
# For each user-workload namespace, label with at least "restricted" policy
# Run on: any machine with kubectl access

NAMESPACE=<NAMESPACE>

kubectl label namespace "$NAMESPACE" \
pod-security.kubernetes.io/enforce=restricted \
--overwrite

kubectl label namespace "$NAMESPACE" \
pod-security.kubernetes.io/audit=restricted \
--overwrite

kubectl label namespace "$NAMESPACE" \
pod-security.kubernetes.io/warn=restricted \
--overwrite
# 4) Optional: Immediately remove any existing pods that are using hostPID
# WARNING: This deletes running pods; review carefully before running.
# 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] | @tsv' | \
while IFS=$'\t' read -r ns name; do
kubectl delete pod "$name" -n "$ns"
done
# 5) Verification (from the benchmark audit)
# Run on: any machine with kubectl access

kubectl get pods --all-namespaces -o json | \
jq -r 'if any(.items[]?; .spec.hostPID == true) then "HOSTPID_FOUND" else "NO_HOSTPID" end'
Automation
#!/usr/bin/env bash
set -euo pipefail

# This script:
# - Adds or updates a PodSecurityPolicy and/or Namespace-level policy object
# that forbids hostPID usage in all user namespaces.
# - Skips system/control-plane namespaces.
# - Verifies that no pods with hostPID=true remain (or reports if any do).
#
# Requirements:
# - Run on any machine with kubectl access to the cluster.
# - kubectl must be configured with sufficient RBAC to create/patch the used objects.
#
# NOTE:
# - This implements the benchmark guidance by restricting admission of hostPID
# containers via namespace-level policy resources.
# - Two mechanisms are covered, use whichever your cluster supports:
# * Pod Security Admission (PSA) labels on Namespaces (v1.23+ clusters using PSA)
# * Or, as a fallback, a restrictive PodSecurityPolicy bound per namespace
#
# The script is idempotent and safe to re-run.

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

# Namespaces to ignore (system/control-plane)
EXCLUDE_NAMESPACES=(
kube-system
kube-public
kube-node-lease
default
)

# Label values for Pod Security Admission (most strict: restricted:enforce)
PSA_ENFORCE_LEVEL="restricted"
PSA_AUDIT_LEVEL="restricted"
PSA_WARN_LEVEL="restricted"

# Names for PSP resources (if PSP is enabled in your cluster)
PSP_NAME="restrict-hostpid-psp"
PSP_CLUSTERROLE_NAME="restrict-hostpid-psp-role"

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

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

have_psa() {
# Heuristic: if PodSecurity admission plugin is present, Namespaces usually
# accept pod-security.kubernetes.io/* labels; there's no direct API.
# We simply try to label a dummy in dry-run mode.
if kubectl label namespace default \
pod-security.kubernetes.io/enforce="${PSA_ENFORCE_LEVEL}" \
--dry-run=server >/dev/null 2>&1; then
return 0
fi
return 1
}

have_psp() {
if kubectl api-resources | grep -q "^podsecuritypolicies"; then
return 0
fi
return 1
}

# ------------------------
# Step 1: Create/update cluster-wide objects for PSP path (if applicable)
# ------------------------

setup_psp() {
# Create or update the PodSecurityPolicy that forbids hostPID
cat <<'EOF' | kubectl apply -f -
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restrict-hostpid-psp
spec:
privileged: false
hostPID: false
hostIPC: false
hostNetwork: 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

# Create a ClusterRole that allows use of this PSP
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ${PSP_CLUSTERROLE_NAME}
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['${PSP_NAME}']
EOF
}

bind_psp_to_namespace() {
local ns="$1"

cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: use-${PSP_NAME}
namespace: ${ns}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ${PSP_CLUSTERROLE_NAME}
subjects:
- kind: Group
name: system:serviceaccounts:${ns}
apiGroup: rbac.authorization.k8s.io
EOF
}

# ------------------------
# Step 2: Apply namespace-level restrictions
# ------------------------

main() {
echo "Discovering namespaces..."
mapfile -t namespaces < <(kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')

use_psa=false
use_psp=false

if have_psa; then
echo "Pod Security Admission detected; will enforce restricted profile via namespace labels."
use_psa=true
elif have_psp; then
echo "PodSecurityPolicy detected; will create PSP and bind per namespace."
use_psp=true
setup_psp
else
echo "WARNING: Neither Pod Security Admission nor PodSecurityPolicy appear to be available."
echo "You must implement an alternative admission control (e.g. OPA/Gatekeeper, Kyverno)"
echo "to enforce 'hostPID: false' per namespace. This script cannot enforce that automatically."
fi

for ns in "${namespaces[@]}"; do
if ns_excluded "$ns"; then
echo "Skipping excluded namespace: ${ns}"
continue
fi

echo "Processing namespace: ${ns}"

if $use_psa; then
# Label namespace with restrictive PSA; idempotent.
kubectl label namespace "${ns}" \
"pod-security.kubernetes.io/enforce=${PSA_ENFORCE_LEVEL}" \
"pod-security.kubernetes.io/audit=${PSA_AUDIT_LEVEL}" \
"pod-security.kubernetes.io/warn=${PSA_WARN_LEVEL}" \
--overwrite
fi

if $use_psp; then
bind_psp_to_namespace "${ns}"
fi
done

# ------------------------
# Step 3: Verification
# ------------------------
echo "Verifying that no pods with hostPID=true remain admitted..."
result="$(
kubectl get pods --all-namespaces -o json | \
jq -r 'if any(.items[]?; .spec.hostPID == true) then "HOSTPID_FOUND" else "NO_HOSTPID" end'
)"

if [[ "$result" == "NO_HOSTPID" ]]; then
echo "Verification PASSED: no pods with hostPID=true are present."
else
echo "Verification WARNING: existing pods with hostPID=true are still present."
echo "New hostPID pods should now be rejected in protected namespaces, but you must:"
echo "- Identify and delete/migrate those pods, or"
echo "- Explicitly allow hostPID only where absolutely required."
fi
}

main "$@"