Skip to main content

The Default Namespace Should Not Be Used

More Info:​

Placing resources in the default namespace prevents applying tailored policies and RBAC boundaries. New resources should be created in specific, purpose-built namespaces.

Risk Level​

Low

Address​

Security

Compliance Standards​

  • CIS GKE

Triage and Remediation​

Remediation​

Manual Steps
  1. List all non-system workloads in the default namespace

    • Run on: any machine with kubectl access
    kubectl get all -n default
  2. Decide target namespaces and create them if needed

    • Run on: any machine with kubectl access
    • Replace team-a, team-b with meaningful names for your org:
    kubectl create namespace team-a
    kubectl create namespace team-b
    • Repeat as required for each logical application/team boundary.
  3. For each non-system resource in default, export its manifest and re‑create it in the correct namespace

    • Run on: any machine with kubectl access
    • Example for a deployment myapp you want in team-a:
    kubectl get deployment myapp -n default -o yaml \
    | sed 's/namespace: default/namespace: team-a/' \
    | sed '/resourceVersion:/d;/uid:/d;/creationTimestamp:/d;/selfLink:/d;/managedFields:/d' \
    | kubectl apply -f -
    kubectl delete deployment myapp -n default
    • Use the same pattern for other types (e.g., svc, statefulset, daemonset, job, cronjob), adjusting the kind and name:
    # example for a service
    kubectl get service myapp-svc -n default -o yaml \
    | sed 's/namespace: default/namespace: team-a/' \
    | sed '/resourceVersion:/d;/uid:/d;/creationTimestamp:/d;/selfLink:/d;/managedFields:/d' \
    | kubectl apply -f -
    kubectl delete service myapp-svc -n default
  4. For workloads that must remain cluster-scoped or in kube-system, do not move them

    • Run on: any machine with kubectl access
    • Before moving anything, confirm it is not a core component:
    kubectl get all -A | grep -E 'kube-system|kube-public|kube-node-lease'
    • If you are unsure whether an object in default is application-specific or required by an addon, consult your platform documentation or application owners before moving it.
  5. Update CI/CD, Helm values, and manifests so new resources are not created in default

    • In all deployment manifests, ensure either:
      • metadata.namespace: <desired-namespace> is set, or
      • You deploy with an explicit namespace flag, for example:
        kubectl apply -n team-a -f app.yaml
    • For Helm charts, set a non-default namespace:
      helm install myapp ./chart-path --namespace team-a --create-namespace
  6. Verification: confirm the default namespace has no non-system resources

    • Run on: any machine with kubectl access
    output=$(kubectl get all -n default --no-headers 2>/dev/null | grep -v '^service\s\+kubernetes\s' || true)
    if [ -z "$output" ]; then echo "DEFAULT_NAMESPACE_UNUSED"; else echo "DEFAULT_NAMESPACE_IN_USE"; fi
Using kubectl

On any machine with kubectl access:

  1. List what is currently in the default namespace (for planning and decisions):
kubectl get all -n default
kubectl get configmap,secret,sa,role,rolebinding,networkpolicy -n default
  1. Create purpose-specific namespaces (example: team-a and platform):
cat << 'EOF' > namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
name: team-a
---
apiVersion: v1
kind: Namespace
metadata:
name: platform
EOF

kubectl apply -f namespaces.yaml
  1. For each workload in default, re-create it in an appropriate namespace. Example for a deployment and service currently in default:

Export manifests (edit to adjust metadata.namespace and any references before applying):

kubectl get deploy my-app -n default -o yaml > my-app-deploy.yaml
kubectl get svc my-app -n default -o yaml > my-app-svc.yaml

Edit both files:

  • Change metadata.namespace: default to metadata.namespace: team-a
  • Remove cluster-assigned fields (status, metadata.resourceVersion, metadata.uid, metadata.creationTimestamp, metadata.managedFields) and any ownerReferences that no longer apply.

Then apply in the target namespace:

kubectl apply -f my-app-deploy.yaml
kubectl apply -f my-app-svc.yaml
  1. After new workloads are running in non-default namespaces and you have validated functionality, delete objects from the default namespace (except the kubernetes service):
# Delete non-kubernetes services in default
kubectl get svc -n default --no-headers | awk '$1 != "kubernetes" {print $1}' | \
xargs -r kubectl delete svc -n default

# Delete deployments, replicasets, pods, jobs, cronjobs, daemonsets, statefulsets
kubectl delete deploy,rs,pod,job,cronjob,ds,sts --all -n default

# Optionally clean up non-system configmaps/secrets/serviceaccounts, after review:
kubectl delete configmap --all -n default
kubectl delete secret --all -n default
kubectl delete sa --all -n default
kubectl delete role,rolebinding,networkpolicy --all -n default
  1. Update your workflows so new resources are not created in default, for example by always specifying -n <namespace> or metadata.namespace in manifests, and by configuring tooling defaults.

  2. Verification (same logic as the audit):

output=$(kubectl get all -n default --no-headers 2>/dev/null | grep -v '^service\s\+kubernetes\s' || true)
if [ -z "$output" ]; then echo "DEFAULT_NAMESPACE_UNUSED"; else echo "DEFAULT_NAMESPACE_IN_USE"; fi
Automation
#!/usr/bin/env bash
set -euo pipefail

# This script requires:
# - kubectl configured and authorized
# - jq and kubectl-neat (optional but recommended for cleaner manifests)

# CONFIGURATION: map old namespace "default" -> new target namespaces by label selector
# Each entry: "label_selector:new_namespace"
# Example: move all pods with app=web to namespace web, etc.
# Adjust to your environment and policies.
MAPPINGS=(
"app=web:web"
"app=api:api"
)

# Helper: ensure a namespace exists
ensure_namespace() {
local ns="$1"
if ! kubectl get namespace "${ns}" >/dev/null 2>&1; then
kubectl create namespace "${ns}"
fi
}

# Helper: move namespaced resources from default to a given namespace based on label selector
migrate_resources() {
local selector="$1"
local target_ns="$2"

ensure_namespace "${target_ns}"

# List all resource types that are namespaced
local types
types=$(kubectl api-resources --namespaced=true --verbs=list -o name)

for t in $types; do
# Skip resources that shouldn't be moved generically
case "$t" in
events.k8s.io/*|events|leases.coordination.k8s.io|leases.coordination.k8s.io/*) continue ;;
esac

# Get resources in default matching selector
local names
names=$(kubectl get "$t" -n default -l "${selector}" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
[ -z "$names" ] && continue

for name in $names; do
# Export resource manifest
if command -v kubectl-neat >/dev/null 2>&1; then
manifest=$(kubectl get "$t" "$name" -n default -o yaml | kubectl-neat)
else
manifest=$(kubectl get "$t" "$name" -n default -o yaml)
fi

# Patch namespace and remove fields that prevent re-create
cleaned=$(printf '%s\n' "$manifest" \
| sed -E '/^(status:|metadata:)/,/^[^ ]/!b' \
| sed -E '/^ (creationTimestamp|resourceVersion|uid|selfLink|generation|managedFields|ownerReferences):/d' \
| sed -E 's/namespace: default/namespace: '"${target_ns}"'/' )

# Apply in target namespace and delete original
printf '%s\n' "$cleaned" | kubectl apply -f -
kubectl delete "$t" "$name" -n default --ignore-not-found
done
done
}

# MAIN

# 1. Migrate resources from default namespace according to mappings
for entry in "${MAPPINGS[@]}"; do
IFS=":" read -r selector ns <<<"$entry"
migrate_resources "$selector" "$ns"
done

# 2. Final verification: ensure default namespace has no workload resources
output=$(kubectl get all -n default --no-headers 2>/dev/null | grep -v '^service\s\+kubernetes\s' || true)

if [ -z "$output" ]; then
echo "DEFAULT_NAMESPACE_UNUSED"
else
echo "DEFAULT_NAMESPACE_IN_USE"
echo "The following resources remain in the default namespace:"
echo "$output"
echo "Review and decide manually where each should be moved, then re-run this script after updating MAPPINGS."
fi