Skip to main content

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​

Manual Steps
  1. 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
  2. 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
  3. Move workload resources (Deployments, StatefulSets, Services, etc.) out of default

    • Run on: any machine with kubectl access
      Export each object from default, 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.yaml
    sed -i '/^\s*namespace: /d' /tmp/my-app-deploy.yaml
    sed -i '/^\s*resourceVersion: /d;/^\s*uid: /d;/^\s*selfLink: /d;/^\s*creationTimestamp: /d;/^\s*managedFields: /,/^\s*ownerReferences: /d' /tmp/my-app-deploy.yaml
    kubectl delete deployment my-app -n default
    kubectl apply -n app-namespace -f /tmp/my-app-deploy.yaml

    Repeat 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.

  4. 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 explicit namespace: default fields, or -n default CLI 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.

  5. 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 of default. Example:
    kubectl get role,rolebinding -n default
    # create equivalent RBAC in the new namespace
    kubectl create rolebinding app-admin-binding \
    --clusterrole=edit \
    --user your-user@example.com \
    -n app-namespace
  6. 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" ]; 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 user resources in the default namespace
kubectl get $(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -) \
--ignore-not-found -n default
  1. For each resource type, decide a target namespace and create it if needed (example: team-a)
kubectl create namespace team-a
  1. Move namespaced resources out of default using kubectl 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
  1. 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