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'