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 Critical Security Controls v8
  • CIS EKS
  • 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 user resources in the default namespace
    Run on: any machine with kubectl access

    kubectl get $(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -) \
    --ignore-not-found -n default
  2. Decide target namespaces and create them if needed
    For each logical application/group of resources, pick or create a dedicated namespace.
    Example (adjust names as needed):

    kubectl create namespace app-frontend
    kubectl create namespace app-backend

    If a namespace already exists, you do not need to recreate it:

    kubectl get namespace
  3. Export manifests of resources currently in the default namespace
    Run once per resource type you want to move; redirect to files for review/edit.
    Examples (adjust types as needed based on step 1 output):

    kubectl get deploy,sts,ds,svc,ing,cm,secret,sa,job,cronjob -n default -o yaml > default-workloads.yaml
    kubectl get pvc -n default -o yaml > default-pvc.yaml

    Review these files in an editor and for each object:

    • Change metadata.namespace: default to the desired namespace.
    • Adjust names/labels/annotations if you need to separate apps into different namespaces.
    • For ServiceAccountName, ConfigMap, Secret, RoleBinding, etc., ensure referenced objects will also exist in the new namespace.
  4. Recreate resources in their new namespaces
    After editing the exported files, apply them:

    kubectl apply -f default-workloads.yaml
    kubectl apply -f default-pvc.yaml

    Confirm they exist in the new namespaces:

    kubectl get all -n app-frontend
    kubectl get all -n app-backend
  5. Delete migrated resources from the default namespace
    Only after confirming that workloads are running correctly in their new namespaces, delete the originals.
    Example (adjust types/names as needed; you can use --all where appropriate):

    kubectl delete deploy,sts,ds,svc,ing,cm,secret,sa,job,cronjob -n default --all
    kubectl delete pvc -n default --all

    Be careful not to delete any system-created objects; inspect with:

    kubectl get all,cm,secret,sa,role,rolebinding,pvc -n default
  6. Verification (no user resources left in default namespace)
    Run on: any machine with kubectl access

    output=$(kubectl get $(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -) --ignore-not-found -n default 2>/dev/null | grep -v "^kubernetes ")
    if [ -z "$output" ]; then
    echo "NO_USER_RESOURCES_IN_DEFAULT"
    else
    echo "USER_RESOURCES_IN_DEFAULT_FOUND: $output"
    fi
Using kubectl

On any machine with kubectl access:

  1. Identify all user resources in the default namespace
kubectl get $(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -) \
--ignore-not-found -n default \
-o wide
  1. For each application, create a dedicated namespace (example: my-app)
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: my-app
EOF
  1. Export and re-apply namespaced resources into the new namespace
    (adapt my-app and resource selectors as needed)
# Example: move deployments, services, configmaps, secrets for one app label
kubectl get deploy,svc,configmap,secret \
-n default -l app=my-app \
-o yaml \
| sed 's/namespace: default/namespace: my-app/g' \
| sed '/^\s*uid:/d;/^\s*resourceVersion:/d;/^\s*creationTimestamp:/d;/^\s*selfLink:/d;/^\s*managedFields:/d' \
| kubectl apply -f -
  1. Delete the old copies from the default namespace
    (ensure the app is healthy in the new namespace first)
kubectl delete deploy,svc,configmap,secret \
-n default -l app=my-app

Repeat steps 2–4 for each application currently using the default namespace, choosing an appropriate namespace name and label selector.

  1. (Optional) Add a warning label/annotation to discourage use of default namespace
kubectl label namespace default usage=discouraged --overwrite
kubectl annotate namespace default \
"note=do-not-deploy-user-workloads-here" \
--overwrite
  1. Verification
output=$(kubectl get $(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -) --ignore-not-found -n default 2>/dev/null | grep -v "^kubernetes ")
if [ -z "$output" ]; then
echo "NO_USER_RESOURCES_IN_DEFAULT"
else
echo "USER_RESOURCES_IN_DEFAULT_FOUND: $output"
fi
Automation
#!/usr/bin/env bash
set -euo pipefail

# Automation for: "The Default Namespace Should Not Be Used"
# Scope: run on any machine with kubectl access and correct context.
#
# Behavior:
# - Detect all namespaced resources currently in the "default" namespace
# - For each resource, interactively choose a non-default target namespace
# (creating it if necessary)
# - Move the resource by exporting manifest, adjusting metadata.namespace
# and metadata.annotations, then re-creating in the target namespace
# and deleting the original
# - Safe to re-run: already-moved resources will not be in "default"
# - Final verification uses the benchmark's audit logic

# --------------- helper functions ---------------

require_kubectl() {
if ! command -v kubectl >/dev/null 2>&1; then
echo "ERROR: kubectl not found in PATH" >&2
exit 1
fi
}

prompt_namespace() {
local current_ns="$1"
local target_ns
while true; do
read -r -p "Enter target namespace for resources currently in '${current_ns}' (not 'default'): " target_ns
target_ns="${target_ns:-}"
if [[ -z "${target_ns}" ]]; then
echo "Namespace cannot be empty."
continue
fi
if [[ "${target_ns}" == "default" ]]; then
echo "Target namespace must not be 'default'."
continue
fi
echo "${target_ns}"
return 0
done
}

ensure_namespace_exists() {
local ns="$1"
if ! kubectl get namespace "${ns}" >/dev/null 2>&1; then
echo "Creating namespace '${ns}'..."
kubectl create namespace "${ns}"
else
echo "Namespace '${ns}' already exists."
fi
}

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

echo "Moving ${kind}/${name} from namespace '${src_ns}' to '${dst_ns}'..."

# Export the resource as YAML, strip status, cluster-specific fields,
# and force target namespace.
tmpfile="$(mktemp)"
trap 'rm -f "${tmpfile}"' RETURN

if ! kubectl get "${kind}" "${name}" -n "${src_ns}" -o yaml > "${tmpfile}"; then
echo " WARN: Failed to get ${kind}/${name} in namespace '${src_ns}', skipping."
return 0
fi

# Clean up fields that prevent re-creation and adjust namespace
# - remove status
# - remove metadata fields that should not be re-applied
# - set metadata.namespace to dst_ns
# Using yq would be nicer but we assume only kubectl and core tools.
# Do minimal, robust transforms with sed/awk.
cleaned="$(mktemp)"
trap 'rm -f "${tmpfile}" "${cleaned}"' RETURN

awk '
$1=="status:"{in_status=1}
!in_status{print}
in_status && NF==0{in_status=0}
' "${tmpfile}" > "${cleaned}"

# Remove certain metadata fields (uid, resourceVersion, etc.)
# This is a best-effort textual cleanup.
sed -i '/^[[:space:]]*uid:/d' "${cleaned}"
sed -i '/^[[:space:]]*resourceVersion:/d' "${cleaned}"
sed -i '/^[[:space:]]*generation:/d' "${cleaned}"
sed -i '/^[[:space:]]*creationTimestamp:/d' "${cleaned}"
sed -i '/^[[:space:]]*managedFields:/,/^[^[:space:]]/d' "${cleaned}"

# Set / override namespace in metadata
if grep -q '^[[:space:]]*namespace:' "${cleaned}"; then
sed -i "s/^\([[:space:]]*namespace:\).*/\1 ${dst_ns}/" "${cleaned}"
else
# Insert namespace under metadata:
awk -v ns="${dst_ns}" '
/^metadata:[[:space:]]*$/ && !added {
print $0
print " namespace: " ns
added=1
next
}
{print}
' "${cleaned}" > "${cleaned}.ns" && mv "${cleaned}.ns" "${cleaned}"
fi

# Annotate that this was moved from default (best-effort)
if ! grep -q 'annotations:' "${cleaned}"; then
awk '
/^metadata:[[:space:]]*$/ && !added {
print $0
print " annotations:"
print " moved-from-default-namespace: \"true\""
added=1
next
}
{print}
' "${cleaned}" > "${cleaned}.ann" && mv "${cleaned}.ann" "${cleaned}"
else
awk '
/^ annotations:[[:space:]]*$/ && !added {
print $0
print " moved-from-default-namespace: \"true\""
added=1
next
}
{print}
' "${cleaned}" > "${cleaned}.ann" && mv "${cleaned}.ann" "${cleaned}"
fi

# Apply into target namespace
if ! kubectl apply -f "${cleaned}" --namespace "${dst_ns}"; then
echo " ERROR: Failed to create ${kind}/${name} in namespace '${dst_ns}'. Original not deleted."
return 1
fi

# Delete original
kubectl delete "${kind}" "${name}" -n "${src_ns}" --ignore-not-found

echo " Moved ${kind}/${name} -> namespace '${dst_ns}'."
}

# --------------- main logic ---------------

require_kubectl

echo "Discovering namespaced resource types..."
resource_types="$(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -)"

if [[ -z "${resource_types}" ]]; then
echo "No namespaced resource types discovered. Nothing to do."
exit 0
fi

echo "Listing resources in the 'default' namespace..."
# Skip the default "kubernetes" service as per audit command
output_raw="$(kubectl get ${resource_types} --ignore-not-found -n default 2>/dev/null | grep -v '^kubernetes ' || true)"

if [[ -z "${output_raw}" ]]; then
echo "No user-defined resources found in the 'default' namespace."
exit 0
fi

echo "User-defined resources currently in 'default' namespace:"
echo "${output_raw}"
echo

target_ns="$(prompt_namespace "default")"
ensure_namespace_exists "${target_ns}"

echo
echo "Moving resources from 'default' to '${target_ns}'..."

# For each resource type, list names in default and move
IFS=',' read -r -a kinds <<< "${resource_types}"

for kind in "${kinds[@]}"; do
# Get "kind/name" list; skip no-resources error
mapfile -t lines < <(kubectl get "${kind}" -n default --ignore-not-found -o name 2>/dev/null || true)
if [[ "${#lines[@]}" -eq 0 ]]; then
continue
fi
for full in "${lines[@]}"; do
# full is of form kind/name
res_name="${full#*/}"
move_resource "${kind}" "${res_name}" "default" "${target_ns}"
done
done

echo
echo "Verifying that no user-defined resources remain in the 'default' namespace..."

verification_output="$(
output=$(kubectl get $(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -) --ignore-not-found -n default 2>/dev/null | grep -v "^kubernetes " || true)
if [ -z "$output" ]; then
echo "NO_USER_RESOURCES_IN_DEFAULT"
else
echo "USER_RESOURCES_IN_DEFAULT_FOUND: $output"
fi
)"

echo "Verification result: ${verification_output}"

if [[ "${verification_output}" == "NO_USER_RESOURCES_IN_DEFAULT" ]]; then
echo "Success: default namespace no longer contains user-defined resources."
exit 0
else
echo "WARNING: Some resources remain in the default namespace. Review output above."
exit 1
fi