Skip to main content

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

Manual Steps
  1. List all workloads in the default namespace (run on any machine with kubectl access):

    kubectl get all -n default
  2. For each Deployment/StatefulSet/DaemonSet/Job/CronJob in default, export its manifest to a file and edit the namespace (run on any machine with kubectl access). Example for a Deployment named my-app:

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

    Edit my-app.yaml and change:

    metadata:
    namespace: default

    to:

    metadata:
    namespace: my-tenant-namespace

    Ensure my-tenant-namespace exists:

    kubectl create namespace my-tenant-namespace
  3. Apply the updated workload manifest in the new namespace (run on any machine with kubectl access):

    kubectl apply -f my-app.yaml
  4. Once you confirm the workload is Running in the new namespace, delete the old object from default (run on any machine with kubectl access). Example:

    kubectl get deploy my-app -n my-tenant-namespace
    kubectl delete deploy my-app -n default
  5. Repeat steps 2–4 for all remaining controllers and standalone Pods in default. For a standalone Pod called my-pod:

    kubectl get pod my-pod -n default -o yaml > my-pod.yaml
    # edit namespace: default -> my-tenant-namespace
    kubectl apply -f my-pod.yaml
    kubectl get pod my-pod -n my-tenant-namespace
    kubectl delete pod my-pod -n default
  6. Verification (run on any machine with kubectl access):

    { 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)"'

    Confirm podCount=0 and is_compliant=true.

Using kubectl

On any machine with kubectl access:

  1. Identify workloads currently in the default namespace
kubectl get all -n default
  1. For each workload type, export its manifest from default and prepare it for a new namespace. Replace my-namespace with your target namespace name (which should already exist or be created ahead of time):

Deployments:

kubectl get deployment -n default -o name | while read obj; do
name="${obj##*/}"
kubectl get "$obj" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "deployment-${name}.yaml"
done

StatefulSets:

kubectl get statefulset -n default -o name | while read obj; do
name="${obj##*/}"
kubectl get "$obj" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "statefulset-${name}.yaml"
done

DaemonSets:

kubectl get daemonset -n default -o name | while read obj; do
name="${obj##*/}"
kubectl get "$obj" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "daemonset-${name}.yaml"
done

Jobs:

kubectl get job -n default -o name | while read obj; do
name="${obj##*/}"
kubectl get "$obj" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "job-${name}.yaml"
done

CronJobs:

kubectl get cronjob -n default -o name | while read obj; do
name="${obj##*/}"
kubectl get "$obj" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "cronjob-${name}.yaml"
done

ReplicaSets (if you have standalone ones you want to keep):

kubectl get rs -n default -o name | while read obj; do
name="${obj##*/}"
kubectl get "$obj" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "replicaset-${name}.yaml"
done

Services, ConfigMaps, Secrets, and ServiceAccounts that belong with those workloads should also be moved:

Services:

kubectl get svc -n default -o name | while read obj; do
name="${obj##*/}"
kubectl get "$obj" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "svc-${name}.yaml"
done

ConfigMaps:

kubectl get configmap -n default -o name | while read obj; do
name="${obj##*/}"
kubectl get "$obj" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "configmap-${name}.yaml"
done

Secrets (exclude built‑ins you don’t want to move, adjust selector as needed):

kubectl get secret -n default --no-headers | awk '{print $1}' | while read name; do
kubectl get secret "$name" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "secret-${name}.yaml"
done

ServiceAccounts (excluding the default one):

kubectl get sa -n default --no-headers | awk '$1!="default"{print $1}' | while read name; do
kubectl get sa "$name" -n default -o yaml \
| sed '/namespace: default/d' \
| sed "s/^metadata:/metadata:\n namespace: my-namespace/" \
> "sa-${name}.yaml"
done
  1. Apply the generated manifests into the new namespace
kubectl apply -f . -n my-namespace

(From a directory containing only the YAMLs you intend to move.)

  1. After confirming the workloads are running correctly in my-namespace, delete them from default.

Deployments, StatefulSets, DaemonSets, Jobs, CronJobs:

kubectl delete deployment,statefulset,daemonset,job,cronjob,rs,svc,configmap,secret,sa \
-n default --all

If you need more control, delete selected objects by name instead of --all.

  1. Verification (from any machine with kubectl access)
{ 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)"'
Automation
#!/usr/bin/env bash
set -euo pipefail

# Automation for: No Workloads Should Run In The default Namespace (AKS)
# Runs on: any machine with kubectl access and current-context pointing to the target cluster.

# CONFIGURATION: set a non-default target namespace where workloads will be moved.
TARGET_NAMESPACE="workloads"

echo "==> Ensuring target namespace '${TARGET_NAMESPACE}' exists"
if ! kubectl get namespace "${TARGET_NAMESPACE}" >/dev/null 2>&1; then
kubectl create namespace "${TARGET_NAMESPACE}"
fi

echo "==> Checking for pods in the 'default' namespace"
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."
else
echo "Found ${POD_COUNT} pod(s) in 'default' namespace. Moving workloads to '${TARGET_NAMESPACE}'."

# Move Deployments
echo "-> Handling Deployments"
kubectl get deploy -n default -o name | while read -r deploy; do
[ -z "${deploy}" ] && continue
echo " - Moving ${deploy}"
kubectl get "${deploy}" -n default -o yaml \
| sed -e "s/namespace: default/namespace: ${TARGET_NAMESPACE}/" \
| kubectl apply -n "${TARGET_NAMESPACE}" -f -
kubectl delete "${deploy}" -n default --ignore-not-found
done

# Move StatefulSets
echo "-> Handling StatefulSets"
kubectl get statefulset -n default -o name | while read -r sts; do
[ -z "${sts}" ] && continue
echo " - Moving ${sts}"
kubectl get "${sts}" -n default -o yaml \
| sed -e "s/namespace: default/namespace: ${TARGET_NAMESPACE}/" \
| kubectl apply -n "${TARGET_NAMESPACE}" -f -
kubectl delete "${sts}" -n default --ignore-not-found
done

# Move DaemonSets
echo "-> Handling DaemonSets"
kubectl get daemonset -n default -o name | while read -r ds; do
[ -z "${ds}" ] && continue
echo " - Moving ${ds}"
kubectl get "${ds}" -n default -o yaml \
| sed -e "s/namespace: default/namespace: ${TARGET_NAMESPACE}/" \
| kubectl apply -n "${TARGET_NAMESPACE}" -f -
kubectl delete "${ds}" -n default --ignore-not-found
done

# Move ReplicaSets (only those not owned by a higher-level controller)
echo "-> Handling standalone ReplicaSets"
kubectl get rs -n default -o jsonpath='{range .items[?(@.metadata.ownerReferences==null)]}{.metadata.name}{"\n"}{end}' \
| while read -r rs; do
[ -z "${rs}" ] && continue
echo " - Moving replicaset/${rs}"
kubectl get rs "${rs}" -n default -o yaml \
| sed -e "s/namespace: default/namespace: ${TARGET_NAMESPACE}/" \
| kubectl apply -n "${TARGET_NAMESPACE}" -f -
kubectl delete rs "${rs}" -n default --ignore-not-found
done

# Move standalone Pods (those not controlled by a workload controller)
echo "-> Handling standalone Pods"
kubectl get pods -n default -o jsonpath='{range .items[?(@.metadata.ownerReferences==null)]}{.metadata.name}{"\n"}{end}' \
| while read -r pod; do
[ -z "${pod}" ] && continue
echo " - Moving pod/${pod}"
kubectl get pod "${pod}" -n default -o yaml \
| sed -e "/^status:$/,\$d" \
-e "s/namespace: default/namespace: ${TARGET_NAMESPACE}/" \
| kubectl apply -n "${TARGET_NAMESPACE}" -f -
kubectl delete pod "${pod}" -n default --ignore-not-found
done
fi

echo "==> Verification (CIS CBP C3.2 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 "==> Completed"