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
Remediation
Manual Steps
-
List all resources currently in the
defaultnamespace (any machine with kubectl access)kubectl get all -n defaultkubectl get configmaps,secrets,ingresses,pvc,pv,networkpolicies,serviceaccounts,roles,rolebindings -n default -
Decide target namespaces and create them if needed (any machine with kubectl access)
Replaceteam-a,team-bwith your chosen names.kubectl create namespace team-akubectl create namespace team-bReview and map each object from
defaultto an appropriate namespace based on application/tenant/owner. -
Export manifests of objects in
defaultand 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.yamlOpen the file and for each object:
- Change
metadata.namespace: defaultto the desired namespace (e.g.team-a). - Confirm that any
RoleBindingsubjects or role references still make sense in the new namespace.
- Change
-
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.yamlOnce 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 --allBe careful not to delete the built‑in
kubernetesservice; the above command omits it because it is a cluster‑scoped service and not usually listed with--all, but verify before confirming any deletions. -
Ensure new workloads are not created in
defaultgoing forward (any machine with kubectl access)- Update CI/CD pipelines and manifests to include
metadata.namespace: <target-namespace>or use--namespacein kubectl/Helm commands. - Optionally create an
LimitRange/ResourceQuotaor anAdmissionPolicy/ValidatingWebhook(if available in your environment) that rejects new non‑system objects in thedefaultnamespace.
- Update CI/CD pipelines and manifests to include
-
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"; fiConfirm the output is:
DEFAULT_NAMESPACE_UNUSED
Using kubectl
On any machine with kubectl access:
- Identify what is using the default namespace
kubectl get all -n default
kubectl get configmaps,secrets,serviceaccounts,roles,rolebindings -n default
- Create a dedicated namespace (example:
team-a)
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: team-a
EOF
- 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.
- 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
- (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.)
- 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