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
Remediation
Manual Steps
-
List all user resources in the default namespace
Run on: any machine with kubectl accesskubectl get $(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -) \--ignore-not-found -n default -
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-frontendkubectl create namespace app-backendIf a namespace already exists, you do not need to recreate it:
kubectl get namespace -
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.yamlkubectl get pvc -n default -o yaml > default-pvc.yamlReview these files in an editor and for each object:
- Change
metadata.namespace: defaultto 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.
- Change
-
Recreate resources in their new namespaces
After editing the exported files, apply them:kubectl apply -f default-workloads.yamlkubectl apply -f default-pvc.yamlConfirm they exist in the new namespaces:
kubectl get all -n app-frontendkubectl get all -n app-backend -
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--allwhere appropriate):kubectl delete deploy,sts,ds,svc,ing,cm,secret,sa,job,cronjob -n default --allkubectl delete pvc -n default --allBe careful not to delete any system-created objects; inspect with:
kubectl get all,cm,secret,sa,role,rolebinding,pvc -n default -
Verification (no user resources left in default namespace)
Run on: any machine with kubectl accessoutput=$(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" ]; thenecho "NO_USER_RESOURCES_IN_DEFAULT"elseecho "USER_RESOURCES_IN_DEFAULT_FOUND: $output"fi
Using kubectl
On any machine with kubectl access:
- 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
- For each application, create a dedicated namespace (example:
my-app)
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: my-app
EOF
- Export and re-apply namespaced resources into the new namespace
(adaptmy-appand 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 -
- 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.
- (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
- 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