The Default Namespace Should Not Be Used
More Info:​
Using the default namespace for user resources hampers resource segregation and fine-grained RBAC. Move user-defined objects into dedicated namespaces.
Risk Level​
Low
Address​
Security
Compliance Standards​
- CIS EKS
Triage and Remediation​
- Remediation
Remediation​
Manual Steps
-
Identify 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 -
Create one or more dedicated namespaces to move resources into
- Run on: any machine with kubectl access
kubectl create namespace app-namespace# repeat with other namespace names as needed -
Move workload resources (Deployments, StatefulSets, Services, etc.) out of default
- Run on: any machine with kubectl access
Export each object fromdefault, remove cluster-assigned fields, and re-create it in the new namespace. Example for a Deployment:
kubectl get deployment my-app -n default -o yaml > /tmp/my-app-deploy.yamlsed -i '/^\s*namespace: /d' /tmp/my-app-deploy.yamlsed -i '/^\s*resourceVersion: /d;/^\s*uid: /d;/^\s*selfLink: /d;/^\s*creationTimestamp: /d;/^\s*managedFields: /,/^\s*ownerReferences: /d' /tmp/my-app-deploy.yamlkubectl delete deployment my-app -n defaultkubectl apply -n app-namespace -f /tmp/my-app-deploy.yamlRepeat this pattern for other namespaced resource kinds (e.g.,
statefulset,daemonset,service,ingress,cronjob,job,configmap,secret,pvc), adjusting the kind and name in the commands. - Run on: any machine with kubectl access
-
Update references that point to the default namespace
- Run on: any machine with kubectl access
Search manifests or Helm values you use to deploy resources and update any explicitnamespace: defaultfields, or-n defaultCLI flags, to the new namespace:
grep -R --line-number "namespace: default" .grep -R --line-number "\-n default" .Redeploy with the corrected manifests/Helm charts so future resources are created in the dedicated namespaces.
- Run on: any machine with kubectl access
-
Review and adjust RBAC for new namespaces
- Run on: any machine with kubectl access
Bind roles to users/service accounts in the new namespaces instead ofdefault. Example:
kubectl get role,rolebinding -n default# create equivalent RBAC in the new namespacekubectl create rolebinding app-admin-binding \--clusterrole=edit \--user your-user@example.com \-n app-namespace - Run on: any machine with kubectl access
-
Verify no user resources remain in the 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" ]; thenecho "NO_USER_RESOURCES_IN_DEFAULT"elseecho "USER_RESOURCES_IN_DEFAULT_FOUND: $output"fi
Using kubectl
On any machine with kubectl access:
- Identify user resources in the
defaultnamespace
kubectl get $(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -) \
--ignore-not-found -n default
- For each resource type, decide a target namespace and create it if needed (example:
team-a)
kubectl create namespace team-a
- Move namespaced resources out of
defaultusingkubectl get -o yaml | sed | kubectl apply
Example for Deployments:
kubectl get deploy -n default -o yaml \
| sed 's/namespace: default/namespace: team-a/g' \
| sed '/^ resourceVersion:/d;/^ uid:/d;/^ creationTimestamp:/d;/^ selfLink:/d;/^ managedFields:/d' \
| kubectl apply -f -
kubectl delete deploy -n default --all
Example for Services (be careful with in-cluster DNS names and consumers):
kubectl get svc -n default -o yaml \
| sed 's/namespace: default/namespace: team-a/g' \
| sed '/^ resourceVersion:/d;/^ uid:/d;/^ creationTimestamp:/d;/^ selfLink:/d;/^ clusterIP:/d;/^ clusterIPs:/d;/^ ipFamilies:/d;/^ ipFamilyPolicy:/d;/^ managedFields:/d' \
| kubectl apply -f -
kubectl delete svc -n default --all
Example for ConfigMaps:
kubectl get configmap -n default -o yaml \
| sed 's/namespace: default/namespace: team-a/g' \
| sed '/^ resourceVersion:/d;/^ uid:/d;/^ creationTimestamp:/d;/^ selfLink:/d;/^ managedFields:/d' \
| kubectl apply -f -
kubectl delete configmap -n default --all
Example for Secrets (excluding default service account tokens; filter by name as needed):
kubectl get secret -n default -o yaml \
| sed 's/namespace: default/namespace: team-a/g' \
| sed '/^ resourceVersion:/d;/^ uid:/d;/^ creationTimestamp:/d;/^ selfLink:/d;/^ managedFields:/d' \
| kubectl apply -f -
# then delete only the secrets you intentionally moved, e.g.:
# kubectl delete secret -n default my-app-secret
Repeat the same pattern for other resource kinds present in default (e.g., statefulset, job, cronjob, ingress, role, rolebinding, networkpolicy, etc.):
kubectl get <kind> -n default -o yaml \
| sed 's/namespace: default/namespace: <target-namespace>/g' \
| sed '/^ resourceVersion:/d;/^ uid:/d;/^ creationTimestamp:/d;/^ selfLink:/d;/^ managedFields:/d' \
| kubectl apply -f -
kubectl delete <kind> -n default --all
- Verification (same logic as the audit):
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
#
# migrate-default-namespace-resources.sh
#
# Purpose:
# - Detect user-defined namespaced resources in the "default" namespace.
# - Create a target namespace (by default "apps") if it does not exist.
# - Migrate supported resource types from "default" to the target namespace.
# - Verify that no user-defined resources remain in the "default" namespace.
#
# Run on:
# - Any machine with kubectl access and appropriate RBAC.
#
# Requirements:
# - bash, kubectl, jq (for JSON processing)
#
# Notes:
# - This script is idempotent: safe to re-run.
# - Some resources cannot be moved (e.g. kubernetes Service, certain CRDs).
# - Carefully review and test in non-production before using on critical clusters.
set -euo pipefail
TARGET_NAMESPACE="${TARGET_NAMESPACE:-apps}" # change via env if desired
DRY_RUN="${DRY_RUN:-false}" # set DRY_RUN=true to see planned actions
#-------------------------------------------------------------------------------
# Helper: run kubectl with common flags
#-------------------------------------------------------------------------------
_k() {
kubectl "$@"
}
#-------------------------------------------------------------------------------
# Step 0: Preconditions
#-------------------------------------------------------------------------------
if ! command -v kubectl >/dev/null 2>&1; then
echo "ERROR: kubectl not found in PATH" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "ERROR: jq not found in PATH (required for JSON manipulation)" >&2
exit 1
fi
echo "Using target namespace: ${TARGET_NAMESPACE}"
echo "DRY_RUN: ${DRY_RUN}"
#-------------------------------------------------------------------------------
# Step 1: Ensure target namespace exists
#-------------------------------------------------------------------------------
if ! _k get namespace "${TARGET_NAMESPACE}" >/dev/null 2>&1; then
echo "Target namespace '${TARGET_NAMESPACE}' does not exist. Creating..."
if [ "${DRY_RUN}" = "true" ]; then
echo "DRY_RUN: kubectl create namespace ${TARGET_NAMESPACE}"
else
_k create namespace "${TARGET_NAMESPACE}"
fi
else
echo "Target namespace '${TARGET_NAMESPACE}' already exists."
fi
#-------------------------------------------------------------------------------
# Step 2: Discover namespaced resource types and objects in default namespace
#-------------------------------------------------------------------------------
echo "Discovering namespaced resource types with resources in 'default'..."
API_RESOURCES=$(
_k api-resources --verbs=list --namespaced=true -o name | paste -sd, -
)
# Raw output of all namespaced resources in default (may include headers)
RAW_OUTPUT=$(
_k get "${API_RESOURCES}" --ignore-not-found -n default 2>/dev/null || true
)
# Filter out the default "kubernetes" Service line
FILTERED_OUTPUT=$(echo "${RAW_OUTPUT}" | grep -v "^kubernetes " || true)
if [ -z "${FILTERED_OUTPUT}" ]; then
echo "NO_USER_RESOURCES_IN_DEFAULT"
exit 0
fi
echo "USER_RESOURCES_IN_DEFAULT_FOUND:"
echo "${FILTERED_OUTPUT}"
echo
#-------------------------------------------------------------------------------
# Step 3: Define which resource types we will migrate
#-------------------------------------------------------------------------------
# This list is intentionally conservative. Add more as needed, after testing.
MIGRATABLE_KINDS=(
pods
deployments.apps
replicasets.apps
statefulsets.apps
daemonsets.apps
jobs.batch
cronjobs.batch
services
configmaps
secrets
serviceaccounts
roles.rbac.authorization.k8s.io
rolebindings.rbac.authorization.k8s.io
ingresses.networking.k8s.io
)
# Build a regex for grep -E
MIGRATABLE_REGEX="$(printf '%s\n' "${MIGRATABLE_KINDS[@]}" | paste -sd'|' -)"
#-------------------------------------------------------------------------------
# Step 4: Collect full resource identifiers (kind/name) from default namespace
#-------------------------------------------------------------------------------
echo "Collecting resources in 'default' that match supported kinds..."
RESOURCE_LIST=$(
echo "${FILTERED_OUTPUT}" \
| awk 'NR>1 {print $1,$2}' \
| awk '{print $1"/"$2}' \
| grep -E "${MIGRATABLE_REGEX}" || true
)
if [ -z "${RESOURCE_LIST}" ]; then
echo "No supported resource types found to migrate; manual review needed."
exit 0
fi
echo "Resources to consider for migration:"
echo "${RESOURCE_LIST}"
echo
#-------------------------------------------------------------------------------
# Step 5: Migrate each resource
#-------------------------------------------------------------------------------
migrate_resource() {
local res="$1" # e.g. deployments.apps/my-deployment
local kind="${res%%/*}"
local name="${res##*/}"
echo "Processing ${kind}/${name} in namespace 'default'..."
# Skip the default "kubernetes" Service explicitly, just in case it appears
if [ "${kind}" = "services" ] && [ "${name}" = "kubernetes" ]; then
echo " Skipping core Service 'kubernetes'."
return 0
fi
# Fetch the object as JSON
local json
if ! json=$(_k get "${kind}" "${name}" -n default -o json 2>/dev/null); then
echo " WARNING: Unable to get ${kind}/${name} from 'default'; skipping."
return 0
fi
# Update metadata.namespace, remove resourceVersion, uid, etc.
local patched
patched=$(echo "${json}" \
| jq '
.metadata.namespace = "'"${TARGET_NAMESPACE}"'" |
del(
.metadata.uid,
.metadata.resourceVersion,
.metadata.selfLink,
.metadata.creationTimestamp,
.metadata.managedFields,
.status
)
')
# Check if object already exists in target namespace (idempotency)
if _k get "${kind}" "${name}" -n "${TARGET_NAMESPACE}" >/dev/null 2>&1; then
echo " ${kind}/${name} already exists in '${TARGET_NAMESPACE}'. Skipping create."
else
echo " Creating ${kind}/${name} in '${TARGET_NAMESPACE}'..."
if [ "${DRY_RUN}" = "true" ]; then
echo "DRY_RUN: kubectl apply -f -"
echo "${patched}"
else
echo "${patched}" | _k apply -f -
fi
fi
# Delete original from default
echo " Deleting original ${kind}/${name} from 'default'..."
if [ "${DRY_RUN}" = "true" ]; then
echo "DRY_RUN: kubectl delete ${kind} ${name} -n default"
else
_k delete "${kind}" "${name}" -n default --ignore-not-found
fi
echo " Done with ${kind}/${name}."
}
echo "Starting migration of resources from 'default' to '${TARGET_NAMESPACE}'..."
while read -r res; do
[ -n "${res}" ] || continue
migrate_resource "${res}"
done <<< "${RESOURCE_LIST}"
echo
echo "Migration pass complete."
#-------------------------------------------------------------------------------
# Step 6: Verification (same logic as the audit command)
#-------------------------------------------------------------------------------
echo "Verifying that no user-defined resources remain in 'default'..."
VERIFY_OUTPUT=$(
output=$(
_k get "$(_k 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 "${VERIFY_OUTPUT}"
if echo "${VERIFY_OUTPUT}" | grep -q "NO_USER_RESOURCES_IN_DEFAULT"; then
exit 0
else
echo "NOTE: Some resources remain in 'default'. Manual review and migration may be required."
exit 1
fi