Azure Restrict Untrusted Workloads
More Info:
The Ability To Manage Rbac For Kubernetes Resources From Azure Gives You The Choice To Manage Rbac For The Cluster Resources Either Using Azure Or Native Kubernetes Mechanisms. When Enabled, Azure Ad Principals Will Be Validated Exclusively By Azure Rbac While Regular Kubernetes Users And Service Accounts Are Exclusively Validated By Kubernetes Rbac
Risk Level
Medium
Address
Security
Compliance Standards
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS AKS
- CIS Critical Security Controls v8
- 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
-
Identify how access is managed today (Azure RBAC vs Kubernetes RBAC)
- On any machine with
azaccess, list the AKS cluster and see if Azure RBAC for Kubernetes is enabled:az aks show \--resource-group <RESOURCE_GROUP_NAME> \--name <CLUSTER_NAME> \--query "{name:name,azureRBACEnabled:azurePortalFqdn != null,rbac:enableRbac,oidcIssuerProfile:oidcIssuerProfile}" \--output json - Also check the (newer) “Azure RBAC for Kubernetes Authorization” flag (if present in your environment):
If your cluster was created with Azure RBAC for Kubernetes Authorization, Azure AD identities are enforced via Azure RBAC and Kubernetes RBAC applies only to Kubernetes-native users/service accounts.az aks show \--resource-group <RESOURCE_GROUP_NAME> \--name <CLUSTER_NAME> \--query "azurePortalFqdn"--output tsv
- On any machine with
-
Enumerate which identities can deploy or modify Pods via Azure RBAC
- On any machine with
azaccess, list role assignments on the AKS cluster and its resource group that may allow pod-level operations:# At cluster scopeaz role assignment list \--scope $(az aks show \--resource-group <RESOURCE_GROUP_NAME> \--name <CLUSTER_NAME> \--query id -o tsv) \--output table# At resource group scope (may indirectly grant cluster management rights)az role assignment list \--resource-group <RESOURCE_GROUP_NAME> \--output table - Review assignments for broad roles such as
Owner,Contributor,Azure Kubernetes Service RBAC Cluster Admin,Azure Kubernetes Service RBAC Admin,Azure Kubernetes Service RBAC Writer. Flag any assignments to untrusted groups, users, service principals, or managed identities.
- On any machine with
-
Enumerate which Kubernetes subjects can deploy or modify Pods via Kubernetes RBAC
- On any machine with
kubectlaccess to the cluster, list all roles and bindings that grant pod permissions:# Cluster-widekubectl get clusterrolebindings -o yaml > /tmp/clusterrolebindings.yamlkubectl get clusterroles -o yaml > /tmp/clusterroles.yaml# Namespacedkubectl get rolebindings -A -o yaml > /tmp/rolebindings.yamlkubectl get roles -A -o yaml > /tmp/roles.yaml - Search for pod-related permissions and broad privileges:
grep -E "pods|pods/exec|pods/attach|pods/portforward|pods/log|\b\*\b" /tmp/clusterroles.yaml /tmp/roles.yamlgrep -E "system:masters|cluster-admin" /tmp/clusterrolebindings.yaml /tmp/rolebindings.yaml
- Identify which users, groups, and service accounts (especially those mapped from Azure AD) can create/modify pods or have
*verbs on*resources.
- On any machine with
-
Classify and review “untrusted” workloads and identities
- With your security/ops stakeholders, define what counts as “untrusted” in your environment: external tenants, contractors, CI/CD from third-party systems, experimental namespaces, etc.
- Map those untrusted tenants/teams to:
- Azure AD groups / service principals / managed identities found in Step 2.
- Kubernetes users, groups, and service accounts found in Step 3 (e.g., specific namespaces or service accounts used by external tools).
- For each untrusted identity, confirm whether it currently has pod create/update/delete privileges or can escalate to such permissions (e.g., via
cluster-adminor broadwriteroles).
-
Adjust access so untrusted identities are strongly isolated and least-privilege
- For Azure RBAC (any machine with
azaccess):- Remove or narrow role assignments that let untrusted identities manage pods or cluster configuration:
az role assignment delete \--assignee <UNTRUSTED_OBJECT_ID_OR_PRINCIPAL_ID> \--role "<ROLE_NAME>" \--scope $(az aks show \--resource-group <RESOURCE_GROUP_NAME> \--name <CLUSTER_NAME> \--query id -o tsv)
- Reassign them to tightly scoped custom roles or to read-only roles where possible.
- Remove or narrow role assignments that let untrusted identities manage pods or cluster configuration:
- For Kubernetes RBAC (any machine with
kubectlaccess):- Remove or edit overly broad bindings that give untrusted identities pod management or
cluster-admin-like access:# Example: remove a risky clusterrolebindingkubectl delete clusterrolebinding <BINDING_NAME># Or edit it to remove an untrusted subjectkubectl edit clusterrolebinding <BINDING_NAME> - Prefer namespace-scoped roles with only the verbs and resources required by the workload, and avoid binding untrusted identities to
cluster-adminor roles withverbs: ["*"]orresources: ["*"].
- Remove or edit overly broad bindings that give untrusted identities pod management or
- For Azure RBAC (any machine with
-
Re-verify effective access and document the decision
- Re-run Step 2 and Step 3 commands to confirm that:
- No untrusted Azure identities hold roles that allow pod or cluster administration beyond what is explicitly approved.
- No untrusted Kubernetes users, groups, or service accounts have pod create/update/delete,
pods/exec, or cluster-wide privileges beyond what is explicitly approved.
- Record which mechanisms you use (Azure RBAC, Kubernetes RBAC, or both), how untrusted tenants are isolated (e.g., dedicated namespaces, restricted roles, network policies), and the rationale for any remaining elevated access so that future reviews can validate this decision.
- Re-run Step 2 and Step 3 commands to confirm that:
Using kubectl
kubectl cannot modify this setting because it is controlled at the Azure AKS managed control plane / cloud-provider RBAC configuration layer, not through Kubernetes API objects. To address this finding, make the required changes in Azure (portal/CLI/IaC) as described in the Manual Steps section.
Automation
#!/usr/bin/env bash
#
# Purpose:
# Assist review of CIS AKS 5.6.1 (Restrict untrusted workloads) by:
# - Listing AKS-specific RBAC integration state (via Azure CLI)
# - Enumerating pods, their serviceAccounts, and key security flags
# - Highlighting pods that are likely “untrusted” based on namespace
#
# Requirements:
# - Azure CLI (az) logged in, with access to the subscription
# - kubectl configured to talk to the AKS cluster
#
# Usage:
# AZ_SUBSCRIPTION_ID="..." \
# AZ_RESOURCE_GROUP="..." \
# AZ_AKS_CLUSTER_NAME="..." \
# ./aks-restrict-untrusted-workloads-audit.sh
#
set -euo pipefail
# ----- CONFIG -----
: "${AZ_SUBSCRIPTION_ID:?Set AZ_SUBSCRIPTION_ID}"
: "${AZ_RESOURCE_GROUP:?Set AZ_RESOURCE_GROUP}"
: "${AZ_AKS_CLUSTER_NAME:?Set AZ_AKS_CLUSTER_NAME}"
# Namespaces that are often more trusted (adjust for your org)
TRUSTED_NS_REGEX='^(kube-system|kube-public|kube-node-lease|gatekeeper-system|azure-arc|azure-.*|flux-system|argo.*|monitoring|logging)$'
# ----- 1. AKS RBAC INTEGRATION STATE -----
echo "=== 1) AKS RBAC / AAD Integration State (cluster-level) ===" >&2
az account set --subscription "${AZ_SUBSCRIPTION_ID}"
az aks show \
--resource-group "${AZ_RESOURCE_GROUP}" \
--name "${AZ_AKS_CLUSTER_NAME}" \
--query '{name:name, enableRBAC:enableRBAC, aadProfile:aadProfile, azureRBAC:azurePortalFqdn, oidcIssuerProfile:oidcIssuerProfile}' \
--output json
cat <<'EOF'
INTERPRETATION:
- "enableRBAC": true
Kubernetes RBAC is enabled (recommended).
- "aadProfile" or related AAD / OIDC fields:
Indicates integration with Azure AD for identity.
- Separate Azure RBAC for Kubernetes Authorization (if enabled in your environment)
means AAD principals are authorized via Azure RBAC, and K8s users/SAs via K8s RBAC.
- POTENTIAL PROBLEM:
* RBAC disabled (enableRBAC: false) => cluster cannot restrict workloads via K8s RBAC.
* No AAD or OIDC integration, yet untrusted users can access the cluster via broad kubeconfig or local accounts.
EOF
# ----- 2. CLUSTER-LEVEL RBAC OBJECTS SNAPSHOT -----
echo
echo "=== 2) Cluster-wide RBAC Objects Snapshot ===" >&2
echo "# ClusterRoles bound at cluster scope:" >&2
kubectl get clusterrolebinding -o wide
echo
echo "# Example: any ClusterRoleBindings that bind 'cluster-admin' to service accounts or groups:" >&2
kubectl get clusterrolebinding -o json \
| jq '.items[]
| select(.roleRef.kind=="ClusterRole" and .roleRef.name=="cluster-admin")
| {name: .metadata.name, subjects: .subjects}'
cat <<'EOF'
INTERPRETATION:
- Look for:
* cluster-admin granted to broad groups (e.g., "system:authenticated", large AAD groups).
* cluster-admin or highly privileged ClusterRoles granted to service accounts in namespaces that run untrusted workloads.
- POTENTIAL PROBLEM:
* Untrusted workloads whose service accounts receive cluster-admin or other broad, write-capable ClusterRoles.
EOF
# ----- 3. POD-LEVEL SECURITY VIEW (per-namespace) -----
echo
echo "=== 3) Pod-level View: ServiceAccount + Security Context ===" >&2
# Header
printf "%-32s %-24s %-40s %-8s %-8s %-10s %-8s %-8s\n" \
"NAMESPACE" "POD" "SERVICEACCOUNT" "RUNASROOT" "PRIV" "ALLOW_PRIVESC" "CAP_ADD" "NS_HOSTPID"
# For each pod, show minimal security data
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
sa: .spec.serviceAccountName,
sc: .spec.securityContext,
ctrs: .spec.containers
}
| .ctrs[]
| {
ns: .ns,
pod: .pod,
sa: .sa,
runAsNonRoot: (.securityContext.runAsNonRoot // .sc.runAsNonRoot // null),
runAsUser: (.securityContext.runAsUser // .sc.runAsUser // null),
privileged: (.securityContext.privileged // false),
allowPrivEsc: (.securityContext.allowPrivilegeEscalation // true),
capAdd: ((.securityContext.capabilities.add // []) | join(",")),
hostPID: (.hostPID // false)
}
| [
.ns,
.pod,
(.sa // "default"),
(if .runAsNonRoot == true then "false" # runAsNonRoot==true => NOT root
elif .runAsUser != null and .runAsUser != 0 then "false"
else "true"
end),
(if .privileged == true then "true" else "false" end),
(if .allowPrivEsc == true then "true" else "false" end),
(if .capAdd == "" then "-" else .capAdd end),
(if .hostPID == true then "true" else "false" end)
]
| @tsv
' | while IFS=$'\t' read -r ns pod sa runasroot priv allowpe capadd hostpid; do
printf "%-32s %-24s %-40s %-8s %-8s %-10s %-8s %-8s\n" \
"$ns" "$pod" "$sa" "$runasroot" "$priv" "$allowpe" "$capadd" "$hostpid"
done
cat <<'EOF'
INTERPRETATION:
- Fields:
RUNASROOT : "true" => likely running as root (no runAsNonRoot or UID override).
PRIV : "true" => privileged container.
ALLOW_PRIVESC : "true" => process can gain more privileges.
CAP_ADD : extra Linux capabilities (e.g., NET_ADMIN, SYS_ADMIN).
NS_HOSTPID : "true" => shares host PID namespace.
- POTENTIAL PROBLEMS (candidates for “untrusted” workloads safety review):
* Pods in untrusted namespaces (e.g., dev, test, shared, multi-tenant) with:
- RUNASROOT = true
- PRIV = true
- ALLOW_PRIVESC = true
- CAP_ADD includes broad caps (SYS_ADMIN, NET_ADMIN, etc.)
- NS_HOSTPID = true
EOF
# ----- 4. HEURISTIC: LIKELY-UNTRUSTED PODS WITH HIGH PRIVILEGE -----
echo
echo "=== 4) Heuristic: Potentially Untrusted & Over-Privileged Pods ===" >&2
echo "(Using namespace regex to approximate 'trusted' vs 'untrusted')" >&2
echo "Trusted namespace regex: ${TRUSTED_NS_REGEX}" >&2
echo >&2
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
sa: .spec.serviceAccountName,
sc: .spec.securityContext,
ctrs: .spec.containers,
hostPID: (.spec.hostPID // false)
}
| .ctrs[]
| {
ns: .ns,
pod: .pod,
sa: .sa,
runAsNonRoot: (.securityContext.runAsNonRoot // .sc.runAsNonRoot // null),
runAsUser: (.securityContext.runAsUser // .sc.runAsUser // null),
privileged: (.securityContext.privileged // false),
allowPrivEsc: (.securityContext.allowPrivilegeEscalation // true),
capAdd: ((.securityContext.capabilities.add // []) | join(",")),
hostPID: .hostPID
}
| . + {
runAsRootFlag: (
if .runAsNonRoot == true then false
elif .runAsUser != null and .runAsUser != 0 then false
else true
end
)
}
| select(.privileged == true
or .allowPrivEsc == true
or .runAsRootFlag == true
or (.capAdd | length > 0)
or .hostPID == true)
| [
.ns,
.pod,
(.sa // "default"),
(if .runAsRootFlag then "true" else "false" end),
(if .privileged then "true" else "false" end),
(if .allowPrivEsc then "true" else "false" end),
(if .capAdd == "" then "-" else .capAdd end),
(if .hostPID then "true" else "false" end)
]
| @tsv
' | while IFS=$'\t' read -r ns pod sa runasroot priv allowpe capadd hostpid; do
if [[ ! "$ns" =~ $TRUSTED_NS_REGEX ]]; then
printf "%-32s %-24s %-40s %-8s %-8s %-10s %-8s %-8s\n" \
"$ns" "$pod" "$sa" "$runasroot" "$priv" "$allowpe" "$capadd" "$hostpid"
fi
done
cat <<'EOF'
INTERPRETATION:
- This list is a heuristic: pods that
* Are not in commonly “trusted” namespaces (tunable via TRUSTED_NS_REGEX),
AND
* Have one or more high-privilege indicators.
- These are prime candidates where:
* The workload may be “untrusted” or multi-tenant.
* You must ensure that:
- AKS cluster access is limited to appropriate AAD principals and/or Azure RBAC for K8s.
- Kubernetes RBAC for the serviceAccounts in these namespaces does NOT grant unnecessary permissions
(especially cluster-admin, wildcard verbs/resources, or access to secrets and CRDs used for infra).
EOF
# ----- 5. RBAC PERMISSIONS FOR SERVICE ACCOUNTS USED BY SUSPICIOUS PODS -----
echo
echo "=== 5) RBAC for ServiceAccounts used by Potentially Untrusted Pods ===" >&2
echo "NOTE: This section only lists bindings; you must interpret least-privilege manually." >&2
echo >&2
# Collect unique service accounts from non-trusted namespaces
SA_LIST=$(kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
sa: (.spec.serviceAccountName // "default")
}
| "\(.ns) \(.sa)"' \
| awk '{print $1"/"$2}' \
| sort -u)
echo "# ServiceAccounts and their RoleBindings/ClusterRoleBindings:" >&2
while read -r ns_sa; do
[ -z "$ns_sa" ] && continue
ns="${ns_sa%%/*}"
sa="${ns_sa##*/}"
if [[ "$ns" =~ $TRUSTED_NS_REGEX ]]; then
continue
fi
echo
echo "== ServiceAccount: ${sa} (namespace: ${ns}) =="
echo "-- RoleBindings in namespace ${ns} --"
kubectl get rolebinding -n "$ns" -o json \
| jq -r --arg ns "$ns" --arg sa "$sa" '
.items[]
| select(.subjects[]? | .kind=="ServiceAccount" and .name==$sa and .namespace==$ns)
| {name: .metadata.name, roleRef: .roleRef, subjects: .subjects}
' || echo " (none)"
echo "-- ClusterRoleBindings referencing this ServiceAccount --"
kubectl get clusterrolebinding -o json \
| jq -r --arg ns "$ns" --arg sa "$sa" '
.items[]
| select(.subjects[]? | .kind=="ServiceAccount" and .name==$sa and .namespace==$ns)
| {name: .metadata.name, roleRef: .roleRef, subjects: .subjects}
' || echo " (none)"
done <<< "${SA_LIST}"
cat <<'EOF'
INTERPRETATION:
- For service accounts used by workloads in non-trusted namespaces:
* Review bound Roles/ClusterRoles.
* Look for:
- cluster-admin, or other very broad roles.
- Roles with:
apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
- Access to secrets, configmaps in sensitive namespaces, CRDs that control infra, etc.
- POTENTIAL PROBLEM:
* Any serviceAccount used by an untrusted or multi-tenant workload that has excessive RBAC rights.
EOF
# ----- 6. VERIFICATION STEP -----
echo
echo "=== 6) Verification / Re-run Instructions ===" >&2
cat <<'EOF'
To re-run this assessment after making changes (e.g., tightening Azure RBAC or K8s RBAC):
1. Ensure AZ_SUBSCRIPTION_ID, AZ_RESOURCE_GROUP, and AZ_AKS_CLUSTER_NAME are set correctly.
2. Run this script again.
3. Compare:
- Section 1: AKS RBAC / AAD integration remains correctly configured.
- Sections 2 & 5: RBAC bindings for service accounts used by potentially untrusted workloads reflect least privilege.
- Sections 3 & 4: Security context of pods in untrusted namespaces is hardened where feasible.
This script does not enforce policy; it surfaces information required for manual review
and decision-making for CIS AKS 5.6.1.
EOF