Skip to main content

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 GKE
  • 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

Manual Steps
  1. List all resources currently in the default namespace (any machine with kubectl access)

    kubectl get all -n default
    kubectl get configmaps,secrets,ingresses,pvc,pv,networkpolicies,serviceaccounts,roles,rolebindings -n default
  2. Decide target namespaces and create them if needed (any machine with kubectl access)
    Replace team-a, team-b with your chosen names.

    kubectl create namespace team-a
    kubectl create namespace team-b

    Review and map each object from default to an appropriate namespace based on application/tenant/owner.

  3. Export manifests of objects in default and edit to use the new namespace(s) (any machine with kubectl access)
    Example for all standard workload resources:

    kubectl get deploy,sts,ds,svc,ingress,cm,secret,sa,role,rolebinding -n default -o yaml > /tmp/default-namespace-resources.yaml

    Open the file and for each object:

    • Change metadata.namespace: default to the desired namespace (e.g. team-a).
    • Confirm that any RoleBinding subjects or role references still make sense in the new namespace.
  4. Recreate resources in the proper namespaces and delete them from default (any machine with kubectl access)
    Apply edited manifests:

    kubectl apply -f /tmp/default-namespace-resources.yaml

    Once workloads are healthy in the new namespaces, delete resources from default:

    kubectl delete deploy,sts,ds,svc,ingress,cm,secret,sa,role,rolebinding -n default --all

    Be careful not to delete the built‑in kubernetes service; the above command omits it because it is a cluster‑scoped service and not usually listed with --all, but verify before confirming any deletions.

  5. Ensure new workloads are not created in default going forward (any machine with kubectl access)

    • Update CI/CD pipelines and manifests to include metadata.namespace: <target-namespace> or use --namespace in kubectl/Helm commands.
    • Optionally create an LimitRange/ResourceQuota or an AdmissionPolicy/ValidatingWebhook (if available in your environment) that rejects new non‑system objects in the default namespace.
  6. Verification (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

    Confirm the output is:

    DEFAULT_NAMESPACE_UNUSED
Using kubectl

On any machine with kubectl access:

  1. Identify what is using the default namespace
kubectl get all -n default
kubectl get configmaps,secrets,serviceaccounts,roles,rolebindings -n default
  1. Create a dedicated namespace (example: team-a)
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: team-a
EOF
  1. Recreate workload resources in the new namespace
    (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs, Services, Ingresses, etc.)

Example for a Deployment:

kubectl get deployment <DEPLOYMENT_NAME> -n default -o yaml \
| sed 's/namespace: default/namespace: team-a/' \
| sed '/resourceVersion:/d' \
| sed '/uid:/d' \
| sed '/creationTimestamp:/d' \
| sed '/selfLink:/d' \
| sed '/managedFields:/,$d' \
| kubectl apply -f -

Example for a Service:

kubectl get service <SERVICE_NAME> -n default -o yaml \
| sed 's/namespace: default/namespace: team-a/' \
| sed '/resourceVersion:/d' \
| sed '/uid:/d' \
| sed '/creationTimestamp:/d' \
| sed '/selfLink:/d' \
| sed '/clusterIP:/!b;n;s/.*/ clusterIP: None/' \
| kubectl apply -f -

(For Services, review clusterIP handling manually; do not blindly apply to production without checking type/annotations.)

Repeat for all non‑system resources currently in default. Update any references (e.g., RoleBindings, NetworkPolicies, Ingress host/rules, external clients) to point to the new namespace.

  1. Delete migrated resources from the default namespace
    Run only after confirming workloads are healthy in the new namespace:
kubectl delete deployment,replicaset,pod,service,statefulset,daemonset,job,cronjob \
--all -n default
kubectl delete configmap,secret,serviceaccount,role,rolebinding \
--all -n default
  1. (Optional) Apply a deny policy to prevent future use of the default namespace (Kubernetes with Gatekeeper example)
cat << 'EOF' | kubectl apply -f -
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoDefaultNamespace
metadata:
name: forbid-default-namespace
spec:
match:
namespaces:
- default
kinds:
- apiGroups: [""]
kinds: ["Pod","Service","ConfigMap","Secret","ServiceAccount"]
EOF

(Requires Gatekeeper and a matching ConstraintTemplate already installed; adjust to your admission controller or omit if not in use.)

  1. Verification (matches the audit logic)
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
#
# Purpose:
# Help enforce “do not use the default namespace” by:
# - Identifying all non-system workloads in the default namespace
# - Optionally migrating them to a target namespace
# - Verifying that the default namespace contains no non-kubernetes service objects
#
# Scope:
# Run on any machine with kubectl access and appropriate RBAC.
#
# Notes:
# - This is a MANUAL benchmark control: there is no universally safe, fully
# automated fix. This script assists review and migration; you must confirm
# each move.
# - Changing namespaces requires delete+recreate of most objects; this script
# generates manifests for review and then applies them to the target namespace.
# - ClusterIP/LoadBalancer/Ingress and other dependencies may need manual
# adjustments. Review carefully before approving migrations.

set -euo pipefail

# --------- CONFIGURATION ---------
# Target namespace to migrate workloads into, e.g. "apps" or "team-a".
# Must be a non-empty string and not "default" or "kube-system".
TARGET_NAMESPACE="${TARGET_NAMESPACE:-apps}"

# Set to "yes" to actually apply the migration after review.
# When "no", manifests are only exported for review.
APPLY_MIGRATION="${APPLY_MIGRATION:-no}"

# Directory to store exported manifests for review.
EXPORT_DIR="${EXPORT_DIR:-./default-namespace-migration}"

# --------- FUNCTIONS ---------

require_kubectl() {
if ! command -v kubectl >/dev/null 2>&1; then
echo "ERROR: kubectl not found in PATH. Install/configure kubectl and re-run." >&2
exit 1
fi
}

validate_target_namespace() {
if [[ -z "${TARGET_NAMESPACE}" ]]; then
echo "ERROR: TARGET_NAMESPACE is empty. Set TARGET_NAMESPACE and re-run." >&2
exit 1
fi
if [[ "${TARGET_NAMESPACE}" == "default" ]] || [[ "${TARGET_NAMESPACE}" == "kube-system" ]]; then
echo "ERROR: TARGET_NAMESPACE must not be 'default' or 'kube-system'." >&2
exit 1
fi
}

ensure_target_namespace_exists() {
if ! kubectl get namespace "${TARGET_NAMESPACE}" >/dev/null 2>&1; then
echo "INFO: Creating namespace '${TARGET_NAMESPACE}'..."
kubectl create namespace "${TARGET_NAMESPACE}"
else
echo "INFO: Namespace '${TARGET_NAMESPACE}' already exists."
fi
}

export_default_namespace_objects() {
mkdir -p "${EXPORT_DIR}"

echo "INFO: Exporting non-kubernetes-service objects from 'default' namespace..."
# Get all namespaced API resources that support 'list' and 'get'
mapfile -t resources < <(
kubectl api-resources --namespaced=true -o name 2>/dev/null | sort -u
)

for res in "${resources[@]}"; do
# Skip events and ephemeral/system-like resources that usually shouldn't be migrated
case "${res}" in
events.events.k8s.io|events|leases.coordination.k8s.io)
continue
;;
esac

# List objects in default namespace, excluding the built-in 'kubernetes' service
objects=$(kubectl get "${res}" -n default --no-headers 2>/dev/null | awk '{print $1}' | grep -v '^kubernetes$' || true)
if [[ -z "${objects}" ]]; then
continue
fi

for obj in ${objects}; do
# Export manifest without cluster-specific fields (server-side apply friendly)
echo "INFO: Exporting ${res}/${obj}..."
kubectl get "${res}" "${obj}" -n default -o yaml \
| sed '/^\s*resourceVersion:/d' \
| sed '/^\s*uid:/d' \
| sed '/^\s*creationTimestamp:/d' \
| sed '/^\s*managedFields:/,/^\s*[^ ]/d' \
> "${EXPORT_DIR}/${res}__${obj}.yaml"
done
done

echo "INFO: Exported manifests are in: ${EXPORT_DIR}"
}

update_manifests_namespace() {
echo "INFO: Updating exported manifests to use namespace '${TARGET_NAMESPACE}'..."
for file in "${EXPORT_DIR}"/*.yaml; do
[[ -e "$file" ]] || continue
# Ensure metadata.namespace is set to TARGET_NAMESPACE
if grep -q '^\s*namespace:\s*default\s*$' "$file"; then
sed -i "s/^\(\s*namespace:\s*\)default\s*$/\1${TARGET_NAMESPACE}/" "$file"
elif ! grep -q '^\s*namespace:\s*' "$file"; then
# Insert namespace under metadata if missing
awk -v ns="${TARGET_NAMESPACE}" '
/^metadata:\s*$/ && !seen {
print $0;
print " namespace: " ns;
seen=1;
next
}
{print $0}
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
fi
done
}

apply_migration_manifests() {
echo "INFO: Applying manifests into '${TARGET_NAMESPACE}'..."
kubectl apply -n "${TARGET_NAMESPACE}" -f "${EXPORT_DIR}"
}

delete_objects_from_default() {
echo "INFO: Deleting migrated objects from 'default' namespace..."

# Derive original resource/kind and name from filenames
for file in "${EXPORT_DIR}"/*.yaml; do
[[ -e "$file" ]] || continue
res=$(basename "$file" | sed 's/\.yaml$//' | awk -F'__' '{print $1}')
name=$(basename "$file" | sed 's/\.yaml$//' | awk -F'__' '{print $2}')

if [[ -n "${res}" && -n "${name}" ]]; then
# Ensure we don't delete the built-in service even if present
if [[ "${res}" == "services" || "${res}" == "service" ]]; then
if [[ "${name}" == "kubernetes" ]]; then
continue
fi
fi
echo "INFO: Deleting ${res}/${name} from 'default'..."
kubectl delete "${res}" "${name}" -n default --ignore-not-found
fi
done
}

verify_default_namespace_unused() {
echo "INFO: Verifying that the default namespace is unused (except for the 'kubernetes' service)..."
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 "Remaining objects in 'default' namespace:"
echo "${output}"
fi
}

# --------- MAIN ---------

require_kubectl
validate_target_namespace
ensure_target_namespace_exists
export_default_namespace_objects
update_manifests_namespace

if [[ "${APPLY_MIGRATION}" == "yes" ]]; then
echo "WARNING: You have set APPLY_MIGRATION=yes."
echo " This will:"
echo " - apply exported manifests into '${TARGET_NAMESPACE}'"
echo " - delete the corresponding objects from 'default'"
read -r -p "Type 'migrate' to proceed, or anything else to abort: " confirm
if [[ "${confirm}" == "migrate" ]]; then
apply_migration_manifests
delete_objects_from_default
else
echo "INFO: Migration aborted by user; manifests are available in ${EXPORT_DIR} for manual review."
fi
else
echo "INFO: APPLY_MIGRATION is 'no'."
echo " Review manifests in ${EXPORT_DIR}, then re-run with APPLY_MIGRATION=yes to perform migration."
fi

verify_default_namespace_unused