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
LowAddress
SecurityCompliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
Manual Steps
- 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}'
- For each tenant namespace without a ResourceQuota, create a baseline manifest file locally (edit the namespace and limits/requests as appropriate):
cat > tenant-resourcequota.yaml << 'EOF'
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-quota
namespace: TENANT_NAMESPACE_NAME
spec:
hard:
requests.cpu: "2"
requests.memory: "4Gi"
limits.cpu: "4"
limits.memory: "8Gi"
pods: "50"
services: "10"
configmaps: "20"
secrets: "50"
persistentvolumeclaims: "10"
EOF
- Apply the ResourceQuota for that tenant namespace (run once per namespace after editing the manifest):
kubectl apply -f tenant-resourcequota.yaml
- (Optional) Review the effective quotas in each tenant namespace to ensure they reflect your capacity and fairness goals:
kubectl describe resourcequota -n TENANT_NAMESPACE_NAME
- Repeat steps 2–4 for every tenant namespace that needs its own tailored resource bounds.
- Verification (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
| ([ $quotas.items[] | select(.metadata.namespace == $m.name) ] | length) as $count
| "kind=Namespace name=\($m.name) resourceQuotas=\($count) is_compliant=\(if $count > 0 then "true" else "false" end)"
][]'
Using kubectl
Using kubectl
# 1) Identify non-compliant “tenant” namespaces (any machine with kubectl access)
kubectl get ns
# Example: assume these are tenant namespaces that need quotas:
# team-a, team-b
# Adjust the list and values for your environment.
# 2) Create a ResourceQuota manifest for each tenant namespace (any machine with kubectl access)
cat <<'EOF' > team-a-resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-quota
namespace: team-a
spec:
hard:
requests.cpu: "4"
requests.memory: "8Gi"
limits.cpu: "8"
limits.memory: "16Gi"
pods: "100"
services: "20"
configmaps: "100"
secrets: "100"
persistentvolumeclaims: "20"
EOF
kubectl apply -f team-a-resourcequota.yaml
cat <<'EOF' > team-b-resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-quota
namespace: team-b
spec:
hard:
requests.cpu: "4"
requests.memory: "8Gi"
limits.cpu: "8"
limits.memory: "16Gi"
pods: "100"
services: "20"
configmaps: "100"
secrets: "100"
persistentvolumeclaims: "20"
EOF
kubectl apply -f team-b-resourcequota.yaml
# 3) Verification (any machine with kubectl access)
# Check ResourceQuota objects exist and review their limits
kubectl get resourcequota --all-namespaces -o wide
# Re-run the benchmark’s audit logic to confirm is_compliant=true
{ 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
Automation
#!/usr/bin/env bash
set -euo pipefail
# This script ensures every tenant namespace in an AKS cluster
# (all namespaces except kube-system, kube-public, kube-node-lease)
# has at least one ResourceQuota object.
#
# Run on: any machine with kubectl access and jq installed.
# Pre-flight checks
command -v kubectl >/dev/null 2>&1 || {
echo "kubectl not found in PATH" >&2
exit 1
}
command -v jq >/dev/null 2>&1 || {
echo "jq not found in PATH" >&2
exit 1
}
# Quota template: adjust limits/requests to your multi-tenant policy.
# This is intentionally conservative; change values before use if needed.
create_quota() {
local ns="$1"
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-default-quota
namespace: ${ns}
spec:
hard:
requests.cpu: "2"
requests.memory: "4Gi"
limits.cpu: "4"
limits.memory: "8Gi"
pods: "100"
services: "50"
configmaps: "50"
secrets: "100"
persistentvolumeclaims: "20"
EOF
}
echo "Discovering tenant namespaces (excluding kube-system, kube-public, kube-node-lease)..."
TENANT_NAMESPACES=$(kubectl get ns -o json \
| jq -r '.items[]
| select(.metadata.name as $n
| ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata.name')
if [ -z "${TENANT_NAMESPACES}" ]; then
echo "No tenant namespaces found; nothing to do."
else
echo "Tenant namespaces:"
printf ' - %s\n' ${TENANT_NAMESPACES}
fi
for ns in ${TENANT_NAMESPACES:-}; do
echo "Processing namespace: ${ns}"
EXISTING_COUNT=$(kubectl get resourcequota -n "${ns}" -o json \
| jq '.items | length')
if [ "${EXISTING_COUNT}" -gt 0 ]; then
echo " Already has ${EXISTING_COUNT} ResourceQuota object(s); skipping creation."
continue
fi
echo " No ResourceQuota found; creating tenant-default-quota..."
create_quota "${ns}"
done
echo
echo "Verification (re-running benchmark-style audit)..."
# Reuse the provided audit logic to confirm compliance
{
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'

