OnCallReady

Lesson 23.31 · Azure II: Networking & AKS · 16 min read

AKS access: Entra ID, Azure RBAC for Kubernetes, local accounts

In plain words

Imagine a sports centre with two checks. At the front desk you show your membership card to get a wristband; that just gets you into the building. Inside, each area, the pool, the gym, the courts, checks what your membership allows. Having the wristband doesn't mean you may use the pool. And the manager keeps an old master key that opens everything without any check, which is exactly why a careful centre throws it away.

AKS access is the same. az aks get-credentials is the front desk, an Azure RBAC check on listClusterUserCredential. What you may do inside is the second check: Kubernetes RBAC with Entra groups, or Azure RBAC roles like AKS RBAC Writer on a namespace. The master key is the local clusterAdmin account, which bypasses Entra ID entirely; --disable-local-accounts removes it.

On your kubeadm cluster (Ch 18) access was a certificate in a kubeconfig. On AKS the company wants people to log in with their Entra accounts, lose access the day they leave, and get only their team's namespace. Getting into an AKS cluster involves two separate permission checks, on two planes again (control plane and data plane, 22.23).

What you need to know already: 15.1 (kubeconfig, contexts), 17.30 (Kubernetes RBAC: Role, RoleBinding), 17.43 (users are certificates), 22.8 (Entra ID, groups), 22.13 (Azure RBAC: roles, scopes, dataActions), 22.18 (groups, PIM), 22.12 (workload identity).

1. Getting a kubeconfig (ARM, control plane)

az aks get-credentials -g rg-oncall-lab -n aks-sysop
Merged "aks-sysop" as current context in /home/learner/.kube/config

az aks get-credentials -g <rg> -n <cluster> downloads a kubeconfig for the cluster. That call is Microsoft.ContainerService/managedClusters/listClusterUserCredential/action, granted by Azure Kubernetes Service Cluster User Role (or Contributor/Owner). It merges into your existing kubeconfig - it does not replace the file - and switches the current context.

On an Entra-integrated cluster the kubeconfig contains no credential, just an exec plugin - a program kubectl runs to fetch a fresh token each time:

users:
- name: clusterUser_rg-oncall-lab_aks-sysop
  user:
    exec:
      command: kubelogin
      args: [get-token, --environment, AzurePublicCloud, --server-id, 6dae42f8-4368-4678-94ff-3960e28e3630, ...]

kubectl runs kubelogin (Microsoft's helper that gets Entra tokens for kubectl) to get a token for the AKS server app (6dae42f8-... is the same in every tenant). kubelogin convert-kubeconfig -l azurecli makes it reuse your az login; automation uses -l workloadidentity or -l spn (a service principal, 22.8).

2. What you may do inside (the Kubernetes API, data plane)

Two models:

/subscriptions/.../managedClusters/aks-sysop                      whole cluster
/subscriptions/.../managedClusters/aks-sysop/namespaces/shop      one namespace
Azure Kubernetes Service RBAC Reader          read most objects (not secrets)
Azure Kubernetes Service RBAC Writer          read/write most objects (not roles)
Azure Kubernetes Service RBAC Admin           everything in the scope, incl. roles
Azure Kubernetes Service RBAC Cluster Admin   everything, everywhere

These are dataActions. Owner of the subscription gets Forbidden from the Kubernetes API on an Azure-RBAC cluster until given one of them - the Key Vault lesson again. Both models can run side by side; Kubernetes RBAC still applies.

So a developer on the shop team needs both: Cluster User Role (to fetch the kubeconfig) and RBAC Writer on namespaces/shop (to deploy). Grant both to the group.

3. The back door: local accounts

Every AKS cluster also has a local clusterAdmin certificate:

az aks get-credentials -g rg-oncall-lab -n aks-sysop --admin

It is cluster-admin, bypasses Entra ID and Azure RBAC entirely, is not tied to a person, and cannot be revoked without rotating the cluster certificates (az aks rotate-certs, which restarts everything). Disable it:

az aks update -g rg-oncall-lab -n aks-sysop --disable-local-accounts
# on a cluster with local accounts disabled
az aks get-credentials -g rg-oncall-lab -n aks-sysop --admin
ERROR: (BadRequest) Getting static credential is not allowed because this cluster is set to disable local accounts.

Before you do, make sure a break-glass path exists (an emergency way in, used only when everything else fails): an Entra group with RBAC Cluster Admin that a PIM activation (22.18) can put you into. With local accounts off and Entra down, there is no way in - that is the trade-off, and it is the right one for production.

Private clusters

--enable-private-cluster puts the API server behind a private endpoint (23.10) in the node resource group (privatelink.<region>.azmk8s.io zone). kubectl then only works from networks that resolve and route to it - a machine inside the VNet, a jump box, or az aks command invoke, which runs kubectl inside the cluster for you.

The two error messages, side by side

# as alice, with no Azure role on the cluster
az aks get-credentials -g rg-oncall-lab -n aks-sysop
ERROR: (AuthorizationFailed) The client 'alice@...' ... does not have authorization to perform
action 'Microsoft.ContainerService/managedClusters/listClusterUserCredential/action' ...

The ARM gate: Alice is missing Cluster User Role (or anything with that action) on the cluster.

# as alice, holding a kubeconfig but no Azure RBAC role on the cluster
kubectl get pods -n shop
Error from server (Forbidden): pods is forbidden: User "[email protected]" cannot
list resource "pods" in API group "" in the namespace "shop": User does not have access to the
resource in Azure. Update role assignment to allow access.

The Kubernetes API gate with Azure RBAC: she has a kubeconfig but no RBAC Reader/Writer at the cluster or the shop namespace scope. The last sentence ("User does not have access to the resource in Azure") is how you know it is Azure RBAC deciding and not a Kubernetes RoleBinding.

Automation

Deploy automation needs the same two things for its identity: Cluster User Role (or it runs az aks command invoke) and an RBAC role at the namespace it deploys to. Use workload identity federation for its identity (22.21, the GitHub OIDC mission) and kubelogin convert-kubeconfig -l workloadidentity (or -l azurecli after az login) - never the --admin credential.

What you can now do

Why it helps

Every developer onboarding ticket and every pipeline that deploys to AKS goes through these two gates, and the error message tells you which one failed: AuthorizationFailed on listClusterUserCredential is the Azure gate, Forbidden ... User does not have access to the resource in Azure is the Kubernetes API gate with Azure RBAC. Knowing that turns a vague "I can't access the cluster" into a one-line fix.

Security reviews will ask about the local admin account, since it's not tied to a person and can't be revoked without rotating certificates. Disabling it, with a PIM-backed break-glass group, is the standard answer. And for pipelines, you'll wire up kubelogin with workload identity instead of the --admin credential that still appears in old runbooks.

FAQ

Why can I get credentials but not list pods?

Because they're two separate checks. az aks get-credentials only needs Azure Kubernetes Service Cluster User Role, or anything with listClusterUserCredential, on the cluster resource; it gives you a kubeconfig without any rights inside. What you may do in the Kubernetes API is decided by Kubernetes RBAC bindings, or, with Azure RBAC enabled, by roles like AKS RBAC Reader or Writer at cluster or namespace scope. You need both.

What is kubelogin?

A client-go credential plugin that gets Entra ID tokens for kubectl. On an Entra-integrated cluster, the kubeconfig contains no credential, just an exec entry that runs kubelogin get-token with the AKS server app ID. kubelogin convert-kubeconfig -l azurecli makes it reuse your az login; pipelines use -l workloadidentity or -l spn. Without it installed, kubectl fails to authenticate against Entra-integrated clusters.

Why disable local accounts?

The local clusterAdmin credential from get-credentials --admin is a certificate with full cluster-admin rights. It bypasses Entra ID and Azure RBAC, isn't tied to a person so actions aren't attributable, can be copied freely, and can only be revoked by rotating the cluster's certificates, which restarts components. Disabling local accounts forces all access through Entra ID, with MFA, conditional access, PIM and audit logs.

Is subscription Owner enough to use kubectl on an Azure RBAC cluster?

No. Owner can fetch the kubeconfig, since it includes the credential action, but the Kubernetes API operations are dataActions granted by the AKS RBAC roles, and Owner has no dataActions. So Owner gets Forbidden from the API server until assigned, for example, Azure Kubernetes Service RBAC Cluster Admin. It's the same control plane versus data plane split as Key Vault and Storage.

What changes with a private cluster?

The API server gets a private endpoint in the node resource group instead of a public address, with a private DNS zone under privatelink.<region>.azmk8s.io. kubectl only works from networks that can resolve and route to it: a self-hosted agent in a peered VNet, a jump box, or VPN. az aks command invoke runs kubectl inside the cluster through Azure, which helps for occasional operations without network access.

In an interview Mid

How do users get access to an Entra-integrated AKS cluster, and what does a developer need to deploy to one namespace?

Two separate gates:

  1. ARM - may you get a kubeconfig? az aks get-credentials needs listClusterUserCredential, from Azure Kubernetes Service Cluster User Role. The kubeconfig holds no credential, only an exec plugin: kubelogin fetches an Entra token each time (kubelogin convert-kubeconfig -l azurecli reuses your az login). Missing it: AuthorizationFailed.
  2. Kubernetes API - may your token do this? Either Kubernetes RBAC with Entra group object ids as subjects, or Azure RBAC for Kubernetes (--enable-azure-rbac): role assignments like RBAC Reader/Writer/Admin, scoped to the cluster or to .../namespaces/shop. They are dataActions, so even a subscription Owner gets Forbidden ("User does not have access to the resource in Azure").

So a shop developer needs Cluster User Role + RBAC Writer on namespaces/shop, both granted to the team's group.

Then close the back door: --admin credentials bypass Entra and RBAC, so --disable-local-accounts - after setting up a break-glass group with Cluster Admin via PIM.

Also asked: Compare Kubernetes RBAC and Azure RBAC for Kubernetes authorization in AKS. · Why would you disable local accounts on an AKS cluster, and what must exist first? · How should automation authenticate to an AKS cluster?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.