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