Skip to main content

Namespaces Should Enforce Pod Security Admission Baseline

More Info:

Verifies each namespace is labeled with pod-security.kubernetes.io/enforce set to baseline or restricted so the built-in Pod Security Admission controller rejects unsafe pods.

Risk Level

High

Address

Security

Compliance Standards

  • Cloudanix Best Practice

Triage and Remediation

Remediation

Manual Steps
  1. On any machine with kubectl access, list all namespaces and identify those without an enforce label or with an incorrect value (excluding system namespaces):

    kubectl get ns --show-labels
  2. For each non-system namespace that should be set to baseline, apply the label:

    kubectl label namespace <NAMESPACE_NAME> pod-security.kubernetes.io/enforce=baseline --overwrite
  3. For each non-system namespace that should be set to restricted, apply the label:

    kubectl label namespace <NAMESPACE_NAME> pod-security.kubernetes.io/enforce=restricted --overwrite
  4. (Optional but recommended) Set the enforce-version label so behavior is consistent across upgrades (replace v1.28 with the API version you target):

    kubectl label namespace <NAMESPACE_NAME> pod-security.kubernetes.io/enforce-version=v1.28 --overwrite
  5. (Optional) If you use GitOps/manifests for namespace definitions, add the labels to the Namespace manifests and re-apply them from any machine with kubectl access:

    apiVersion: v1
    kind: Namespace
    metadata:
    name: <NAMESPACE_NAME>
    labels:
    pod-security.kubernetes.io/enforce: baseline # or restricted
    pod-security.kubernetes.io/enforce-version: v1.28

    Apply:

    kubectl apply -f <namespace-manifest>.yaml
  6. Verification (on any machine with kubectl access): run the same audit logic and confirm all non-exempt namespaces show enforce=baseline or enforce=restricted with is_compliant=true:

    kubectl get namespaces -o json | jq -r '
    [ .items[]
    | select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
    | .metadata as $m
    | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
    | (($m.labels // {})["pod-security.kubernetes.io/enforce"] // "") as $lvl
    | "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"
    + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
    + (if $labels == "" then "" else " labels=\($labels)" end)
    + " enforce=\(if $lvl == "" then "none" else $lvl end)"
    + " is_compliant=\(if ($lvl == "baseline" or $lvl == "restricted") then "true" else "false" end)"
    ] as $rows
    | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Using kubectl

On any machine with kubectl access:

  1. Create a manifest labeling all existing non-system namespaces with baseline (edit to restricted if desired):
kubectl get ns \
--no-headers \
| awk '!/^(kube-system|kube-public|kube-node-lease)[[:space:]]/ {print $1}' \
| xargs -I{} echo "---\napiVersion: v1\nkind: Namespace\nmetadata:\n name: {}\n labels:\n pod-security.kubernetes.io/enforce: baseline" \
> enforce-psa-namespaces.yaml
  1. Apply the manifest:
kubectl apply -f enforce-psa-namespaces.yaml
  1. For any new namespace you create, include the label in its manifest, for example:
apiVersion: v1
kind: Namespace
metadata:
name: my-secure-namespace
labels:
pod-security.kubernetes.io/enforce: baseline

Apply it with:

kubectl apply -f my-secure-namespace.yaml
  1. Verification (same logic as the audit, using kubectl):
kubectl get namespaces -o json | jq -r '
[ .items[]
| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (($m.labels // {})["pod-security.kubernetes.io/enforce"] // "") as $lvl
| "name=\($m.name) enforce=\(if $lvl == "" then "none" else $lvl end)"
+ " is_compliant=\(if ($lvl == "baseline" or $lvl == "restricted") then "true" else "false" end)"
][]'
Automation
#!/usr/bin/env bash
#
# Enforce Pod Security Admission level (baseline or restricted) on all applicable namespaces.
# Platform: Azure AKS
# Requirements: kubectl, jq in PATH; current context set to target cluster.

set -euo pipefail

# -----------------------------
# Configuration
# -----------------------------
# Desired enforcement level: "baseline" or "restricted"
DESIRED_LEVEL="baseline"

# Namespaces to exclude from labeling (system namespaces)
EXCLUDE_NS=("kube-system" "kube-public" "kube-node-lease")

# -----------------------------
# Helper functions
# -----------------------------

contains() {
local e match="$1"; shift
for e; do
if [[ "$e" == "$match" ]]; then
return 0
fi
done
return 1
}

# -----------------------------
# Main logic
# -----------------------------

echo "Fetching namespaces..."
ALL_NS_JSON="$(kubectl get namespaces -o json)"

# Build list of namespaces to process
MAPFILE -t TARGET_NAMESPACES < <(
echo "$ALL_NS_JSON" | jq -r '.items[].metadata.name' |
while read -r ns; do
if contains "$ns" "${EXCLUDE_NS[@]}"; then
continue
fi
echo "$ns"
done
)

if [[ "${#TARGET_NAMESPACES[@]}" -eq 0 ]]; then
echo "No namespaces to process (only excluded namespaces found)."
else
echo "Will ensure label pod-security.kubernetes.io/enforce=${DESIRED_LEVEL} on namespaces:"
printf ' %s\n' "${TARGET_NAMESPACES[@]}"
fi

# Apply label in an idempotent way
for ns in "${TARGET_NAMESPACES[@]}"; do
# Check current value
current_val="$(kubectl get ns "$ns" -o jsonpath='{.metadata.labels.pod-security\.kubernetes\.io/enforce}' 2>/dev/null || true)"
if [[ "$current_val" == "$DESIRED_LEVEL" ]]; then
echo "Namespace '$ns' already has pod-security.kubernetes.io/enforce=${DESIRED_LEVEL}; skipping."
continue
fi

echo "Labeling namespace '$ns' with pod-security.kubernetes.io/enforce=${DESIRED_LEVEL}..."
kubectl label namespace "$ns" "pod-security.kubernetes.io/enforce=${DESIRED_LEVEL}" --overwrite
done

# -----------------------------
# Verification (same logic as audit)
# -----------------------------

echo
echo "Verification (CIS CBP C3.3):"

kubectl get namespaces -o json | jq -r '
[ .items[]
| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| (($m.labels // {})["pod-security.kubernetes.io/enforce"] // "") as $lvl
| "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ " enforce=\(if $lvl == "" then "none" else $lvl end)"
+ " is_compliant=\(if ($lvl == "baseline" or $lvl == "restricted") then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'