Skip to main content

Manage Kubernetes RBAC Users With Azure AD

More Info:

Integrate Azure Active Directory with Kubernetes RBAC so cluster access is governed by centrally managed Azure AD identities and group memberships.

Risk Level

High

Address

Security

Compliance Standards

  • CIS AKS

Triage and Remediation

Remediation

Manual Steps
  1. Determine your AKS cluster’s auth model (Azure AD or local accounts)

    • On any machine with az access, run:
      az aks show \
      --resource-group <AKS_RESOURCE_GROUP> \
      --name <AKS_CLUSTER_NAME> \
      --query "{aadProfile:aadProfile, oidcIssuerProfile:oidcIssuerProfile, apiServerAccessProfile:apiServerAccessProfile}" \
      --output json
    • Review the output:
      • If aadProfile is null or empty and there is no oidcIssuerProfile in use with Azure AD Workload Identity, the cluster is not using Azure AD for client auth.
      • If aadProfile.enableAzureRBAC is true, AKS-managed Azure RBAC is used instead of Kubernetes-native RBAC; decide if this aligns with your org’s RBAC model.
  2. Identify how kubectl clients currently authenticate (Azure AD vs static credentials)

    • On any machine with kubectl access, inspect the kubeconfig being used:
      kubectl config view --minify --raw
    • Look at the users: section:
      • user.exec.command: az or kubelogin and exec.args with get-token indicate Azure AD/OIDC-based auth.
      • user.token, client-certificate-data, or static username/password indicate non–Azure AD credentials (service accounts, client certs, or basic auth), which should be minimized for human users.
  3. Review current Kubernetes RBAC subjects for non–Azure AD identities

    • On any machine with kubectl access, list RBAC bindings and look for User subjects that are not Azure AD principals (for clusters already wired to AAD, user names are typically AAD UPNs or Object IDs):
      kubectl get clusterrolebindings,rolebindings -A -o json \
      | jq '.items[].subjects // [] | .[] | select(.kind=="User")' 2>/dev/null
    • Flag any:
      • Local usernames or generic names (e.g., admin, kube-admin, devops) not traceable to Azure AD.
      • Tokens/certs issued outside Azure AD (service accounts are fine for workloads but not for human users).
    • Decide which of these should be re-mapped to Azure AD users or groups and plan to deprecate the non–AAD identities.
  4. Verify Azure AD groups mapped into Kubernetes RBAC (if AAD is enabled)

    • On any machine with kubectl access, inspect role bindings targeting Group subjects (these should correspond to Azure AD groups when Azure AD integration is used):
      kubectl get clusterrolebindings,rolebindings -A -o json \
      | jq '.items[].subjects // [] | .[] | select(.kind=="Group")' 2>/dev/null
    • For each name value, confirm in the Azure portal (Azure AD → Groups) or via CLI:
      az ad group show --group "<GROUP_NAME_OR_OBJECT_ID>"
    • Ensure:
      • Group membership is centrally managed in Azure AD (no per-user bindings where group-based bindings would work).
      • Privileged roles (e.g., bound to cluster-admin) are restricted to tightly controlled Azure AD groups.
  5. If Azure AD is not integrated, plan and configure AAD-based access

    • Decide, with your identity/security team, the target integration model:
      • AKS-managed Azure AD with Azure RBAC for Kubernetes authorization, or
      • AKS with AAD for auth and Kubernetes-native RBAC for authorization.
    • Implement using Azure documentation and IaC where possible. For example, to enable AKS-managed Azure AD and Azure RBAC on a new cluster (conceptual example; adapt to your environment):
      az aks create \
      --resource-group <AKS_RESOURCE_GROUP> \
      --name <AKS_CLUSTER_NAME> \
      --enable-aad \
      --enable-azure-rbac \
      --aad-admin-group-object-ids <AAD_ADMIN_GROUP_OBJECT_ID> \
      --node-count 3 \
      --generate-ssh-keys
    • For existing clusters, review whether migration to Azure AD–backed auth is supported for your cluster version and apply changes using az aks update or redeploy via ARM/Bicep/Terraform per your standards.
  6. Verify that Kubernetes RBAC is now governed by Azure AD identities

    • On any machine with az access, obtain credentials using Azure AD:
      az aks get-credentials \
      --resource-group <AKS_RESOURCE_GROUP> \
      --name <AKS_CLUSTER_NAME> \
      --overwrite-existing
    • Confirm that the active user is an Azure AD identity:
      kubectl config view --minify -o jsonpath='{.users[0].user.exec.command}{" "}{.users[0].user.exec.args[*]}' && echo
    • Validate RBAC using Azure AD groups by attempting a namespaced operation as a member of an AAD group bound via RBAC (adjust namespace and group binding you configured):
      kubectl auth can-i get pods --namespace kube-system
    • Confirm that:
      • Access outcomes (allowed/denied) match the Azure AD user/group memberships.
      • Human user access no longer relies on non–Azure AD static users/tokens, except for explicitly justified break-glass or automation accounts you documented.
Using kubectl

kubectl cannot be used to configure Azure AD integration for AKS; this setting is managed in the Azure portal / Azure CLI / IaC at the cloud-control-plane level. Refer to the Manual Steps section for guidance on how to review and configure Azure AD–based authentication and RBAC for your cluster.

Automation
#!/usr/bin/env bash
#
# Purpose:
# Assess whether Kubernetes API access is integrated with Azure AD
# and whether RBAC is used (vs legacy mechanisms).
#
# Run on:
# Any machine with:
# - az CLI authenticated to the correct subscription/tenant (if AKS)
# - kubectl configured for the target cluster context

set -euo pipefail

echo "=== Context information ==="
echo "kubectl current-context:"
kubectl config current-context || echo "ERROR: kubectl not configured"

echo
echo "API server version:"
kubectl version --short || echo "ERROR: cannot reach API server"

echo
echo "=== 1) Check if RBAC is enabled ==="
# For managed AKS, this is usually always enabled on modern clusters,
# but we confirm by inspecting API objects.
echo "- ClusterRoleBindings (sample):"
kubectl get clusterrolebindings.authorization.k8s.io -o name | head -n 10 || true

echo
echo "- Any legacy ABAC-related configuration present in apiserver config (if exposed as ConfigMap)?"
echo " (This is best-effort; many managed control planes do not expose it.)"
kubectl get configmap -n kube-system -o name 2>/dev/null | grep -i 'apiserver' || echo " No obvious apiserver configmap found (expected for AKS)."

echo
echo "Interpretation:"
echo " - Presence of ClusterRoles/ClusterRoleBindings indicates RBAC is in use."
echo " - If your provider documented ABAC support and you still rely on it, that is a concern."

echo
echo "=== 2) AKS-specific: Check AAD integration (requires az CLI) ==="
echo "Attempting to detect AKS clusters in current subscription..."
SUB_ID="$(az account show --query id -o tsv 2>/dev/null || echo '')"
TENANT_ID="$(az account show --query tenantId -o tsv 2>/dev/null || echo '')"

if [ -z "$SUB_ID" ]; then
echo "WARNING: az CLI not logged in or not installed; skipping AKS AAD checks."
else
echo "Using subscription: $SUB_ID"
echo "Tenant: $TENANT_ID"
echo

# List AKS clusters
echo "Listing AKS clusters and their AAD integration status:"
az aks list --query '[].{name:name, rg:resourceGroup, aadProfile: aadProfile, oidcIssuerProfile:oidcIssuerProfile}' -o table || {
echo "ERROR: Unable to list AKS clusters."
}

echo
echo "For the current kubectl context (if AKS), determine matching AKS cluster (best-effort):"
CURRENT_SERVER="$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null || echo '')"
if [ -n "$CURRENT_SERVER" ]; then
echo " API server URL from kubeconfig: $CURRENT_SERVER"
echo
echo " NOTE: Match this URL with AKS cluster FQDNs from: az aks list -o table"
echo " to identify which AKS cluster your current context represents."
else
echo " Could not determine API server URL from kubeconfig."
fi

fi

echo
echo "=== 3) Inspect Kubernetes subjects used in RBAC bindings ==="
echo "Goal: Identify whether RBAC binds directly to local users/service accounts or"
echo " to well-known Azure AD identities/groups (based on naming conventions)."
echo

echo "- All ClusterRoleBindings with their subjects:"
kubectl get clusterrolebinding -o json | jq -r '
.items[] |
.metadata.name as $crb |
.subjects[]? |
"\($crb)\t\(.kind)\t\(.apiGroup // "")\t\(.name)\t\(.namespace // "")"
' 2>/dev/null | column -t || echo "No ClusterRoleBindings or jq missing."

echo
echo "- All RoleBindings (namespace-scoped) with their subjects:"
kubectl get rolebinding --all-namespaces -o json | jq -r '
.items[] |
.metadata.namespace as $ns |
.metadata.name as $rb |
.subjects[]? |
"\($ns)\t\($rb)\t\(.kind)\t\(.apiGroup // "")\t\(.name)"
' 2>/dev/null | column -t || echo "No RoleBindings or jq missing."

echo
echo "Interpretation of RBAC subject listings (manual review):"
echo " Potential problems / items to investigate:"
echo " - Subjects of kind 'User' with opaque, non-AAD style names (e.g. 'admin', 'kube-admin')."
echo " - Bindings to broad or legacy users instead of Azure AD groups."
echo " - ClusterRoleBindings that grant cluster-admin or highly privileged roles to:"
echo " * Individual users instead of AAD groups"
echo " * ServiceAccounts in non-system namespaces without clear justification"
echo " - Any subjects that you cannot map to identities or groups in your Azure AD tenant."

echo
echo "Examples of desirable patterns (for reference during manual review):"
echo " - Subjects of kind 'Group' whose names match Azure AD group object IDs or UPN-style names,"
echo " and which are documented in your IAM model."
echo " - Minimal direct User bindings; preference for group-based access tied to AAD."

echo
echo "=== 4) Optional: enumerate ServiceAccounts with cluster-wide privileges ==="
echo "These are not AAD-based, but often used for automation and should be documented."
echo

echo "- ServiceAccounts bound to cluster-admin:"
kubectl get clusterrolebinding -o json 2>/dev/null | jq -r '
.items[] |
select(.roleRef.kind=="ClusterRole" and .roleRef.name=="cluster-admin") |
.metadata.name as $crb |
.subjects[]? |
select(.kind=="ServiceAccount") |
"\($crb)\t\(.namespace)\t\(.name)"
' 2>/dev/null | column -t || echo "No cluster-admin bindings to ServiceAccounts found (or jq missing)."

echo
echo "Interpretation:"
echo " - Any ServiceAccount with cluster-admin should be explicitly justified and documented."
echo " - For human access, prefer Azure AD-backed identities and groups rather than ServiceAccounts."

echo
echo "=== How to interpret overall results ==="
cat <<'EOF'
You must make a manual decision on whether the cluster meets the intent:

1) RBAC usage:
- If RBAC is not being used (no ClusterRole/Role bindings, or provider docs show ABAC is active),
this is a problem. Migration to RBAC and AAD integration should be planned.

2) Identity source:
- If kubectl access is granted via local kubeconfig users that are NOT backed by Azure AD:
* e.g., client-certificate users, static admin credentials, 'clusterAdmin' accounts,
or users you cannot see in Azure AD
then this indicates the cluster is not centrally governed by Azure AD.

3) RBAC bindings:
- If high-privilege roles (cluster-admin, admin) are bound directly to:
* Individual users instead of groups, or
* Identities not present/managed in Azure AD,
this is a concern. Access should be shifted to Azure AD groups and identities.

4) AKS AAD integration (if AKS):
- From the 'az aks list' output:
* If aadProfile is null/empty for the relevant AKS cluster,
the API server is not integrated with Azure AD.
* If aadProfile is present but you still see non-AAD users in Role/ClusterRole bindings,
verify that:
- Users actually authenticate via Azure AD, and
- Bindings reference the correct AAD users/groups.

No fully automated remediation is possible. Use this script's output to:
- Inventory who/what has access via RBAC.
- Cross-check these identities and groups against Azure AD.
- Decide which bindings should be migrated to Azure AD groups or removed.
EOF