No Workloads Should Run In The default Namespace
More Info:
Verifies the default namespace has no workloads so RBAC, quotas and NetworkPolicies can be scoped per tenant.
Risk Level
Medium
Address
Security
Compliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all workloads in the
defaultnamespace (run on any machine with kubectl access):kubectl get all -n default -
For each workload type in
default, export its manifests to files so you can recreate them in a new namespace (replace WORKLOAD and NAME accordingly; run on any machine with kubectl access):# Example for a Deploymentkubectl get deployment NAME -n default -o yaml > NAME-deploy.yaml# Example for a StatefulSetkubectl get statefulset NAME -n default -o yaml > NAME-sts.yaml# Example for a DaemonSetkubectl get daemonset NAME -n default -o yaml > NAME-ds.yaml# Example for a Jobkubectl get job NAME -n default -o yaml > NAME-job.yaml# Example for a CronJobkubectl get cronjob NAME -n default -o yaml > NAME-cronjob.yaml# Example for a Servicekubectl get service NAME -n default -o yaml > NAME-svc.yaml -
Edit each exported manifest file to set a purpose-specific namespace and remove default-assigned fields (run on any machine with kubectl access):
- In each YAML file, under
metadata, set:namespace: my-tenant-namespace - Remove the following fields if present to avoid conflicts when recreating:
metadata: { uid, resourceVersion, selfLink, creationTimestamp, managedFields, ownerReferences }statussections
- Save the edited files.
- In each YAML file, under
-
Create the new namespace if it does not already exist (run on any machine with kubectl access):
kubectl create namespace my-tenant-namespace -
Recreate workloads in the new namespace, then delete them from
default(run on any machine with kubectl access):# Apply the edited manifests in the new namespacekubectl apply -f NAME-deploy.yamlkubectl apply -f NAME-sts.yamlkubectl apply -f NAME-ds.yamlkubectl apply -f NAME-job.yamlkubectl apply -f NAME-cronjob.yamlkubectl apply -f NAME-svc.yaml# After confirming the workloads are running correctly in the new namespace,# delete the old workloads from the default namespacekubectl delete deployment NAME -n defaultkubectl delete statefulset NAME -n defaultkubectl delete daemonset NAME -n defaultkubectl delete job NAME -n defaultkubectl delete cronjob NAME -n defaultkubectl delete service NAME -n default -
Verification (run on any machine with kubectl access):
{ kubectl get pods -n default -o jsonkubectl get namespace default -o json} | jq -rs '.[0] as $pods | .[1] |.metadata as $m| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels| (($pods.items // []) | length) as $count| "kind=Namespace name=default uid=\($m.uid) apiVersion=v1"+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)+ (if $labels == "" then "" else " labels=\($labels)" end)+ " podCount=\($count)"+ " is_compliant=\(if $count == 0 then "true" else "false" end)"'Confirm that
podCount=0andis_compliant=true.
Using kubectl
On any machine with kubectl access:
- Identify all workloads in the
defaultnamespace
kubectl get all -n default
- For each workload type, export its manifest from
defaultand save it to a file, then edit the namespace field.
Example for a deployment named my-app:
# Export existing manifest
kubectl get deployment my-app -n default -o yaml > my-app-deploy.yaml
# Edit the manifest: in metadata, set
# namespace: my-tenant-namespace
# If no namespace field exists under metadata, add:
# namespace: my-tenant-namespace
#
# Also review and update any other namespaced references (ConfigMaps, Services, RBAC, etc.)
- Create the target namespace if it does not already exist:
kubectl create namespace my-tenant-namespace
- Apply the updated manifest into the new namespace:
kubectl apply -f my-app-deploy.yaml
- Once you have recreated all needed workloads in their new, purpose-specific namespaces and confirmed they are running correctly, delete the originals from the
defaultnamespace.
Examples by resource type:
# Deployments
kubectl delete deployment my-app -n default
# StatefulSets
kubectl delete statefulset my-stateful-app -n default
# DaemonSets
kubectl delete daemonset my-daemon -n default
# CronJobs
kubectl delete cronjob my-cronjob -n default
# Jobs (if still present)
kubectl delete job my-job -n default
# Services, ConfigMaps, Secrets, etc., that were only for these workloads
kubectl delete service my-app -n default
kubectl delete configmap my-app-config -n default
kubectl delete secret my-app-secret -n default
Repeat this export–edit–apply–delete process for every workload that currently runs in the default namespace, moving each into an appropriate purpose-specific namespace.
- Verification (pod count in
defaultshould be zero):
kubectl get pods -n default
Automation
#!/usr/bin/env bash
set -euo pipefail
# This script:
# - Finds all workload resources (Pods, Deployments, ReplicaSets, StatefulSets,
# DaemonSets, Jobs, CronJobs) in the "default" namespace.
# - For each, creates a copy in a target namespace and deletes the original.
# - Verifies that the "default" namespace has no Pods remaining.
#
# REQUIREMENTS:
# - Run on any machine with kubectl access and permissions to list/get/create/delete
# resources cluster-wide.
# - kubectl must be configured to point at the target GKE cluster.
#
# USAGE:
# ./move-default-workloads.sh <target-namespace>
#
# The target namespace MUST exist beforehand and be prepared with appropriate
# RBAC, ResourceQuotas, and NetworkPolicies.
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <target-namespace>" >&2
exit 1
fi
TARGET_NS="$1"
echo "==> Verifying target namespace '${TARGET_NS}' exists"
if ! kubectl get namespace "${TARGET_NS}" >/dev/null 2>&1; then
echo "ERROR: target namespace '${TARGET_NS}' does not exist." >&2
echo "Create it first, for example:" >&2
echo " kubectl create namespace ${TARGET_NS}" >&2
exit 1
fi
echo "==> Checking for workloads in the 'default' namespace"
# If there are no pods, we consider the check already compliant and exit early.
POD_COUNT="$(kubectl get pods -n default --no-headers 2>/dev/null | wc -l | tr -d ' ')"
if [[ "${POD_COUNT}" -eq 0 ]]; then
echo "No pods found in 'default' namespace. Nothing to move."
echo "Cluster is already compliant with respect to this control."
exit 0
fi
echo "Workloads found in 'default' namespace. Beginning migration to '${TARGET_NS}'."
# Resource types to migrate.
# These cover the common workload controllers; adjust if you use additional kinds.
RESOURCE_KINDS=(
"deployment.apps"
"replicaset.apps"
"statefulset.apps"
"daemonset.apps"
"job.batch"
"cronjob.batch"
"pod" # standalone pods not managed by controllers
)
for KIND in "${RESOURCE_KINDS[@]}"; do
echo "==> Processing kind: ${KIND}"
# List resource names; ignore "No resources found" errors.
MAPFILE -t RESOURCES < <(kubectl get "${KIND}" -n default -o name 2>/dev/null || true)
if [[ "${#RESOURCES[@]}" -eq 0 ]]; then
echo " No ${KIND} resources in 'default' namespace."
continue
fi
for RES in "${RESOURCES[@]}"; do
NAME="${RES#*/}" # strip the kind prefix, e.g. deployment.apps/my-deploy -> my-deploy
echo " -> Migrating ${KIND} '${NAME}' from 'default' to '${TARGET_NS}'"
# Check if it already exists in the target namespace (idempotency).
if kubectl get "${KIND}" -n "${TARGET_NS}" "${NAME}" >/dev/null 2>&1; then
echo " Target '${KIND}/${NAME}' already exists in '${TARGET_NS}'."
echo " Skipping creation; will only ensure original is removed."
else
# Export the object from 'default', adjust namespace, and recreate in target namespace.
# Strip fields that should not be migrated (status, resourceVersion, uid, etc.).
echo " Creating '${KIND}/${NAME}' in '${TARGET_NS}'"
kubectl get "${KIND}" -n default "${NAME}" -o json \
| jq '
del(
.metadata.uid,
.metadata.resourceVersion,
.metadata.selfLink,
.metadata.creationTimestamp,
.metadata.generation,
.metadata.annotations."kubectl.kubernetes.io/last-applied-configuration",
.status
)
| .metadata.namespace = "'"${TARGET_NS}"'"
' \
| kubectl apply -f -
fi
# Delete original from default namespace (idempotent: delete succeeds even if already gone).
echo " Deleting original '${KIND}/${NAME}' from 'default'"
kubectl delete "${KIND}" -n default "${NAME}" --ignore-not-found=true
done
done
echo "==> Waiting for any terminating pods in 'default' to fully disappear"
# Wait loop: break when no pods remain.
for i in {1..30}; do
REMAINING="$(kubectl get pods -n default --no-headers 2>/dev/null | wc -l | tr -d ' ')"
if [[ "${REMAINING}" -eq 0 ]]; then
break
fi
echo " ${REMAINING} pod(s) still present in 'default'; rechecking in 10s..."
sleep 10
done
echo "==> Final verification (matches benchmark audit style)"
{ kubectl get pods -n default -o json
kubectl get namespace default -o json
} | jq -rs '
.[0] as $pods | .[1] |
.metadata as $m
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| (($pods.items // []) | length) as $count
| "kind=Namespace name=default uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ " podCount=\($count)"
+ " is_compliant=\(if $count == 0 then "true" else "false" end)"'
echo "==> Migration complete."