Skip to main content

The Default Namespace Should Not Be Used

More Info:

Kubernetes provides a default namespace, where objects are placed if no namespace is specified for them. Placing objects in this namespace makes application of RBAC and other controls more difficult.

Risk Level

Low

Address

Security

Compliance Standards

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS AKS
  • CIS Critical Security Controls v8
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • Essential 8
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • UK NCSC Cyber Assessment Framework

Triage and Remediation

Remediation

Manual Steps
  1. List all non-system resources in the default namespace
    Run on: any machine with kubectl access

    kubectl get all -n default
  2. Create target namespaces for workloads (if they don’t already exist)
    Run on: any machine with kubectl access
    Repeat for each logical application or component:

    kubectl create namespace my-namespace

    (If the namespace exists, this will fail harmlessly; you can ignore the error.)

  3. Migrate workload resources out of the default namespace
    Run on: any machine with kubectl access
    For each resource in default that you want to keep (example: deployment my-app to my-namespace):

    kubectl get deployment my-app -n default -o yaml \
    | sed 's/namespace: default/namespace: my-namespace/' \
    | kubectl apply -f -

    kubectl delete deployment my-app -n default

    Repeat the same pattern for other namespaced resource types in default (e.g., service, statefulset, daemonset, job, cronjob, configmap, secret, pvc), adjusting the kind and name each time:

    kubectl get service my-service -n default -o yaml \
    | sed 's/namespace: default/namespace: my-namespace/' \
    | kubectl apply -f -

    kubectl delete service my-service -n default
  4. Update any manifests or automation to stop using the default namespace
    Run on: any machine with access to your manifest/CI/CD repos

    • Edit Kubernetes manifests to either:
      • Remove explicit namespace: default fields, or
      • Replace them with the appropriate namespace: my-namespace.
    • Update CI/CD or scripts that call kubectl with -n default (or no -n when they rely on default) to use the correct namespaces, for example:
      kubectl apply -n my-namespace -f my-app-deployment.yaml
  5. Optionally restrict or monitor use of the default namespace
    Run on: any machine with kubectl access

    • Create an admission control policy (e.g., a ValidatingWebhookConfiguration or Gatekeeper/kyverno policy) to alert or block new objects in the default namespace.
    • This is cluster- and tool-specific and must be designed according to your existing policy tooling.
  6. Verify the default namespace is no longer used for workloads
    Run on: any machine with kubectl access

    output=$(kubectl get all -n default --no-headers 2>/dev/null | grep -v '^service\s\+kubernetes\s' || true)
    if [ -z "$output" ]; then echo "DEFAULT_NAMESPACE_UNUSED"; else echo "DEFAULT_NAMESPACE_IN_USE"; fi
Using kubectl

On any machine with kubectl access:

  1. Identify all user workloads currently in the default namespace (excluding the built‑in kubernetes service):
kubectl get all -n default
kubectl get configmaps,secrets,ingress,networkpolicies,serviceaccounts,role,rolebinding -n default
  1. Create a replacement namespace (example name prod-apps; adjust as needed):
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: prod-apps
EOF
  1. For each workload type in default, re‑apply its manifest into the new namespace, then delete from default. Below are examples; repeat for all relevant resources after exporting their current YAML.

Deployments:

kubectl get deployment -n default -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete deployment -n default --all

StatefulSets:

kubectl get statefulset -n default -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete statefulset -n default --all

DaemonSets:

kubectl get daemonset -n default -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete daemonset -n default --all

Services (excluding the built‑in kubernetes service):

kubectl get service -n default --field-selector metadata.name!=kubernetes -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete service -n default --all --ignore-not-found

Ingress:

kubectl get ingress -n default -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete ingress -n default --all

ConfigMaps and Secrets (non‑service‑account):

kubectl get configmap -n default -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete configmap -n default --all

kubectl get secret -n default --field-selector 'type!=kubernetes.io/service-account-token' -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete secret -n default --all --field-selector 'type!=kubernetes.io/service-account-token'

ServiceAccounts, Roles, RoleBindings (if present and not cluster‑default ones you intentionally keep):

kubectl get serviceaccount -n default -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete serviceaccount -n default --all

kubectl get role -n default -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete role -n default --all

kubectl get rolebinding -n default -o yaml \
| sed 's/namespace: default/namespace: prod-apps/' \
| kubectl apply -f -
kubectl delete rolebinding -n default --all
  1. Ensure future workloads use non‑default namespaces by updating your manifests’ metadata:
metadata:
name: my-app
namespace: prod-apps
  1. Verification (same logic as the audit):
output=$(kubectl get all -n default --no-headers 2>/dev/null | grep -v '^service\s\+kubernetes\s' || true)
if [ -z "$output" ]; then echo "DEFAULT_NAMESPACE_UNUSED"; else echo "DEFAULT_NAMESPACE_IN_USE"; fi
Automation
#!/usr/bin/env bash
# Purpose: Ensure the default namespace is not used for user workloads.
# Runs on: any machine with kubectl access and current-context set to the target cluster.

set -euo pipefail

# CONFIGURATION: list target namespaces and resource types to migrate.
# Edit as needed; safe to re-run.
TARGET_NAMESPACE_PREFIX="app-"
TARGET_INFRA_NAMESPACE="infra-system"
RESOURCE_TYPES=(
deployments
statefulsets
daemonsets
cronjobs
jobs
pods
services
configmaps
secrets
serviceaccounts
roles
rolebindings
)

kubectl_get() {
kubectl "$@" 2>/dev/null
}

ensure_namespace() {
local ns="$1"
if ! kubectl_get get namespace "${ns}" -o name >/dev/null; then
kubectl create namespace "${ns}"
fi
}

move_resource() {
local type="$1" name="$2" src_ns="$3" dst_ns="$4"

# Skip if already gone (idempotence)
if ! kubectl_get get "${type}" "${name}" -n "${src_ns}" -o name >/dev/null; then
return 0
fi

# Export, rewrite namespace, re-apply into target namespace
kubectl_get get "${type}" "${name}" -n "${src_ns}" -o yaml \
| sed -E "s/^( )?namespace: ${src_ns}$/\1namespace: ${dst_ns}/" \
| kubectl apply -f -

# Delete from default after successful apply
kubectl delete "${type}" "${name}" -n "${src_ns}" --ignore-not-found
}

echo "[INFO] Ensuring target namespaces exist..."
# Example strategy: one namespace per app (deployment) and a shared infra namespace.
ensure_namespace "${TARGET_INFRA_NAMESPACE}"

# Create namespaces based on deployment names (app-<name>)
for deploy in $(kubectl_get get deployments -n default -o jsonpath='{.items[*].metadata.name}'); do
ensure_namespace "${TARGET_NAMESPACE_PREFIX}${deploy}"
done

echo "[INFO] Migrating resources from 'default' namespace..."

# Migrate workload and infra resources
for type in "${RESOURCE_TYPES[@]}"; do
names=$(kubectl_get get "${type}" -n default -o jsonpath='{.items[*].metadata.name}')
for name in $names; do
# Decide destination namespace:
# - If there is a deployment with the same name, use app-<name>
# - Otherwise, consider it infra and move to infra-system
if kubectl_get get deployment "${name}" -n default -o name >/dev/null 2>&1; then
dst_ns="${TARGET_NAMESPACE_PREFIX}${name}"
else
dst_ns="${TARGET_INFRA_NAMESPACE}"
fi

ensure_namespace "${dst_ns}"
echo "[INFO] Moving ${type}/${name} from default -> ${dst_ns}"
move_resource "${type}" "${name}" default "${dst_ns}" || {
echo "[WARN] Failed to move ${type}/${name} from default. Skipping."
}
done
done

echo "[INFO] Cleaning up completed Jobs in default (if any)..."
kubectl delete jobs -n default --field-selector status.successful>0 --ignore-not-found >/dev/null 2>&1 || true

echo "[INFO] Verification: checking that default namespace is unused (except core service/kubernetes)..."
output=$(kubectl_get get all -n default --no-headers 2>/dev/null | grep -v '^service\s\+kubernetes\s' || true)
if [ -z "$output" ]; then
echo "DEFAULT_NAMESPACE_UNUSED"
exit 0
else
echo "DEFAULT_NAMESPACE_IN_USE"
echo "[DETAILS]"
echo "${output}"
exit 1
fi