Skip to main content

Tenant Namespaces Should Have A ResourceQuota

More Info:

Advisory: create a ResourceQuota per tenant namespace to bound aggregate CPU, memory and object counts, preventing one tenant from starving others.

Risk Level

Low

Address

Security

Compliance Standards

  • Cloudanix Best Practice

Triage and Remediation

Remediation

Manual Steps
  1. Identify tenant namespaces (run on any machine with kubectl access):

    kubectl get namespaces \
    --no-headers \
    | awk '!/kube-system|kube-public|kube-node-lease/ {print $1}'
  2. For each tenant namespace that has no ResourceQuota, create a baseline ResourceQuota manifest file (run on any machine with kubectl access). Example for namespace tenant-a:

    cat > tenant-a-resourcequota.yaml << 'EOF'
    apiVersion: v1
    kind: ResourceQuota
    metadata:
    name: tenant-a-quota
    namespace: tenant-a
    spec:
    hard:
    requests.cpu: "2"
    requests.memory: "4Gi"
    limits.cpu: "4"
    limits.memory: "8Gi"
    pods: "50"
    services: "10"
    configmaps: "50"
    persistentvolumeclaims: "10"
    secrets: "100"
    replicationcontrollers: "20"
    resourcequotas: "1"
    services.loadbalancers: "5"
    services.nodeports: "5"
    EOF

    Adjust the hard values to match your tenant’s agreed limits.

  3. Apply the ResourceQuota for that tenant namespace (run on any machine with kubectl access):

    kubectl apply -f tenant-a-resourcequota.yaml
  4. Repeat steps 2–3 for each remaining tenant namespace, changing the name, namespace, and quota values as appropriate.

  5. (Optional) Review existing quotas in tenant namespaces to ensure they match expectations (run on any machine with kubectl access):

    kubectl get resourcequota --all-namespaces -o wide
  6. Verify compliance (run on any machine with kubectl access):

    { kubectl get resourcequotas --all-namespaces -o json
    kubectl get namespaces -o json
    } | jq -rs '
    .[0] as $quotas | .[1] |
    [ .items[]
    | select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
    | .metadata as $m
    | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
    | ([ $quotas.items[] | select(.metadata.namespace == $m.name) ] | length) as $count
    | "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"
    + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
    + (if $labels == "" then "" else " labels=\($labels)" end)
    + " resourceQuotas=\($count)"
    + " is_compliant=\(if $count > 0 then "true" else "false" end)"
    ] as $rows
    | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'

    Confirm that all tenant namespaces now show is_compliant=true.

Using kubectl
# 1) Identify tenant namespaces (run on any machine with kubectl access)
kubectl get ns \
--no-headers \
| awk '!/kube-system|kube-public|kube-node-lease/ {print $1}'

Create a ResourceQuota manifest per tenant namespace. Example for a tenant namespace tenant-a:

# tenant-a-resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-quota
namespace: tenant-a
spec:
hard:
requests.cpu: "4"
limits.cpu: "8"
requests.memory: "8Gi"
limits.memory: "16Gi"
pods: "50"
services: "20"
configmaps: "50"
secrets: "50"
persistentvolumeclaims: "20"
services.loadbalancers: "5"
services.nodeports: "5"

Apply it:

# 2) Apply ResourceQuota for tenant-a (run on any machine with kubectl access)
kubectl apply -f tenant-a-resourcequota.yaml

Repeat with adjusted names/values for each tenant namespace.

Verification (adapted from the audit):

# 3) Confirm every tenant namespace has at least one ResourceQuota
{ kubectl get resourcequotas --all-namespaces -o json
kubectl get namespaces -o json
} | jq -rs '
.[0] as $quotas | .[1] |
[ .items[]
| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ $quotas.items[] | select(.metadata.namespace == $m.name) ] | length) as $count
| "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ " resourceQuotas=\($count)"
+ " is_compliant=\(if $count > 0 then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
# Create a default ResourceQuota for every tenant namespace that lacks one.
# Run on: any machine with kubectl access and current context pointing at the target GKE cluster.

set -euo pipefail

# -----------------------------
# Configuration (edit as needed)
# -----------------------------

# Namespaces to skip (system / non-tenant)
EXCLUDED_NAMESPACES=(
"kube-system"
"kube-public"
"kube-node-lease"
)

# Default ResourceQuota template (cluster-wide defaults; adjust for your tenants)
# This is applied as-is to any non-excluded namespace that currently has 0 ResourceQuota objects.
read -r -d '' RQ_TEMPLATE_JSON << 'EOF'
{
"apiVersion": "v1",
"kind": "ResourceQuota",
"metadata": {
"name": "tenant-default-quota"
},
"spec": {
"hard": {
"requests.cpu": "4",
"requests.memory": "8Gi",
"limits.cpu": "8",
"limits.memory": "16Gi",
"pods": "50",
"services": "10",
"configmaps": "20",
"persistentvolumeclaims": "10",
"secrets": "50"
}
}
}
EOF

# -----------------------------
# Helper functions
# -----------------------------

is_excluded_ns() {
local ns="$1"
for e in "${EXCLUDED_NAMESPACES[@]}"; do
if [[ "$ns" == "$e" ]]; then
return 0
fi
done
return 1
}

# -----------------------------
# Main logic
# -----------------------------

echo "Discovering namespaces..."
NAMESPACES=$(kubectl get namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')

# Track whether we made any changes
CHANGED=0

while IFS= read -r NS; do
[[ -z "$NS" ]] && continue

if is_excluded_ns "$NS"; then
echo "Skipping excluded namespace: $NS"
continue
fi

# Count existing ResourceQuotas in this namespace
COUNT=$(kubectl get resourcequota -n "$NS" --no-headers 2>/dev/null | wc -l | tr -d ' ')

if [[ "$COUNT" -gt 0 ]]; then
echo "Namespace '$NS' already has $COUNT ResourceQuota object(s); leaving as-is."
continue
fi

echo "Creating default ResourceQuota in namespace '$NS'..."
echo "$RQ_TEMPLATE_JSON" | kubectl apply -n "$NS" -f -
CHANGED=1
done <<< "$NAMESPACES"

if [[ "$CHANGED" -eq 0 ]]; then
echo "No changes made; all tenant namespaces already have at least one ResourceQuota."
fi

echo
echo "Verification (post-fix audit):"
echo "------------------------------------------------------------"
# Re-run the benchmark-style audit to confirm is_compliant=true for all rows.
{ kubectl get resourcequotas --all-namespaces -o json
kubectl get namespaces -o json
} | jq -rs '
.[0] as $quotas | .[1] |
[ .items[]
| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ $quotas.items[] | select(.metadata.namespace == $m.name) ] | length) as $count
| "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ " resourceQuotas=\($count)"
+ " is_compliant=\(if $count > 0 then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
echo "------------------------------------------------------------"
echo "Ensure all tenant namespaces show is_compliant=true above."