Get to Grips with Azure AD and AKS

Microsoft has done a lot of work to integrate their Azure Kubernetes Service (AKS) with Azure Active Directory. However, there have been several iterations of this work and several different features that make this work. This can be pretty confusing if you’re coming to this for the first time, so in this article, we will take a look at how this all works and how the different features work together.

The focus here is on using Azure AD for users managing and using your Kubernetes clusters through Kubectl and other tools. Using Azure AD for authentication for your applications running on AKS is not covered but is no different to how you would do so on apps running on any other platform.

Authentication

The first thing we will look at is authentication; how do we use Azure AD to confirm that the user connecting to AKS is who they say they are. For this, we want to look at using the Azure AD integration for AKS. There have been two versions of this, and we want to look at the second iteration, the “managed” integration. This is a much simpler approach where all of the connectivity to AAD is handled for you. If you find yourself following a tutorial that has you create service principles for this integration, you are looking at version 1, and you should stop and switch to version 2.

Pre-Requisites

There are a few pre-requisites that you need to meet to enable this, but most clusters running a recent version should be ok:

  • You must be using Kubernetes 1.18.1 or newer
  • You must have RBAC enabled

You also need to have at least one AAD group setup containing the admins you want to access the AKS cluster. You can add more groups in different roles later, and we will cover this in the authorisation section.

Enable AAD Integration at deploy time

If you are deploying a new cluster, you can enable AAD integration when you deploy the cluster by setting the correct flag. Using the Azure CLI, your cluster create command would look something like this:

az aks create -g myResourceGroup -n myManagedCluster --enable-aad --aad-admin-group-object-ids <group id> 

Replace with the object ID of the AAD group you created that contains your admin users.

If you’re using Infrastructure as Code to create your cluster (and you really should be), then you can set this as a property on the AKS cluster object. Here’s what it looks like in Bicep; you can translate this to other languages as required.

resource aks 'Microsoft.ContainerService/managedClusters@2021-09-01' = {
  name: clusterName
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    dnsPrefix: clusterName
    enableRBAC: true
    aadProfile: {
      adminGroupObjectIDs: [ 
        <admin object id>
       ]
      managed: true
    }
    ...

This will then deploy a new cluster with AAD integration enabled.

Enable AAD Integration on existing clusters

To enable AAD integration on an existing cluster, you can either update your IaC to enable the property or run the AZ CLI command below:

az aks update -g MyResourceGroup -n MyManagedCluster --enable-aad --aad-admin-group-object-ids <group id> 

Accessing an AAD Integrated cluster

Once you have AAD integration enabled, users can use their AAD credentials to connect and obtain a Kubeconfig file. To allow them to do this, you need to grant the user the “Azure Kubernetes Service Cluster User” RBAC role in Azure. Once users have been assigned this role, they can then use the az aks get-credentials command to get their appropriate credential file. Once they do that, the first time they attempt to run a Kubectl command, they will be asked to log in to Azure AD. At this point, any conditional access policies, MFA etc., will apply as usual.

At this point, the user will probably get an error about not having the rights to run the command they want. This is because we have only tackled authentication. We need to move on to Authorisation now, which we will cover in the next section. If the user is in the admin group you added earlier, they will be able to run commands as they have already been granted the cluster-admin role.

Admin Access

Currently, it is still possible to use the az aks get-credentials command with the --admin flag to download the cluster-admin credentials. These sit outside of Azure AD and give you direct admin access to the cluster. This is only possible if the user has been added to the “Azure Kubernetes Service Cluster Admin” role. For some, this is a valuable feature that provides some break-glass access should there be an issue with Azure AD. For others, this is a security concern.

It is now possible to disable the local admin account option if you wish. When you create or update a cluster using the CLI, you can add the --disable-local-accounts flag. For IaC you can use the disableLocalAccounts property:


resource aks 'Microsoft.ContainerService/managedClusters@2021-09-01' = {
  name: clusterName
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    dnsPrefix: clusterName
    enableRBAC: true
    disableLocalAccounts: bool
    aadProfile: {
      adminGroupObjectIDs: [ 
        <admin object id>
       ]
      managed: true
    }
    ...

Authorisation

Now that our users can access the AKS cluster, we need to give them roles to do the work they need to. We can do this two ways; using Kubernetes RBAC or Azure RBAC, you can choose which one works best for you.

Kubernetes RBAC

This option uses the existing Kubernetes RBAC principles and has all of the Authorisation managed inside Kubernetes, edited using Kubectl or similar. This approach is best if you prefer using Kubernetes to manage user rights, or maybe you are creating these role assignments as part of your deployment process using Helm or similar.

Roles or ClusterRoles are created the same way as usual, using standard Kubernetes YAML, there is nothing AAD specific there. Where we do see AAD specific things is with the role binding or cluster role binding. Here we need to use either the object ID of an AAD group for a group binding (recommended) or the object ID or UPN of a user for a user binding. Note that the UPN case does matter.

Group Binding

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: dev-user-access
  namespace: dev
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: dev-user-full-access
subjects:
- kind: Group
  namespace: dev
  name: <groupObjectId>

User Binding

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: clusterrolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: clusterrole
subjects:
- kind: User
  name: <UPN or AAD user object id>

Once these are created in your cluster, your users should be able to access the objects you have provided rights for.

Azure RBAC

An alternative to using Kubernetes RBAC is to use Azure AD RBAC. Here you can assign Azure RBAC roles to your users in the portal (or PowerShell, CLI etc.) that define Kubernetes access. Behind the scenes, Azure then synchronises these with Kubernetes. This approach is best if you want to see all of your users rights in a single place and manage them using familiar Azure concepts.

Note that users still need to have the Azure Kubernetes Service Cluster User role to be able to download their credential file, in addition to any other roles you want to grant them.

To use this, you need to enable this on your AKS cluster. Using the CLI, you can add the --enable-azure-rbac flag. For IaC it’s the enableAzureRBAC property inside the aadProfile section.


resource aks 'Microsoft.ContainerService/managedClusters@2021-09-01' = {
  name: clusterName
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    dnsPrefix: clusterName
    enableRBAC: true
    disableLocalAccounts: bool
    aadProfile: {
      adminGroupObjectIDs: [ 
        <admin object id>
       ]
      enableAzureRBAC: true
      managed: true
    }
    ...

Once this is enabled, you can use Azure AD RBAC to assign Kubernetes specific roles to users on the AKS object in the portal (or CLI, PowerShell, Inc). There are four built-in roles that you can use out of the box:

RoleDescription
Azure Kubernetes Service RBAC ReaderAllows read-only access to see most objects in a namespace. It doesn’t allow viewing roles or role bindings. This role doesn’t allow viewing Secrets, since reading the contents of Secrets enables access to ServiceAccount credentials in the namespace, which would allow API access as any ServiceAccount in the namespace (a form of privilege escalation)
Azure Kubernetes Service RBAC WriterAllows read/write access to most objects in a namespace. This role doesn’t allow viewing or modifying roles or role bindings. However, this role allows accessing Secrets and running Pods as any ServiceAccount in the namespace, so it can be used to gain the API access levels of any ServiceAccount in the namespace.
Azure Kubernetes Service RBAC AdminAllows admin access, intended to be granted within a namespace. Allows read/write access to most resources in a namespace (or cluster scope), including the ability to create roles and role bindings within the namespace. This role doesn’t allow write access to resource quota or to the namespace itself.
Azure Kubernetes Service RBAC Cluster AdminAllows super-user access to perform any action on any resource. It gives full control over every resource in the cluster and in all namespaces.

If these roles are too broad, you can create custom roles using the normal process to create Azure RBAC custom roles. You can see the complete list of available permissions you can assign for Kubernetes roles here, the ones that control access to the Kubernetes API all start with Microsoft.ContainerService/managedClusters/APIs/ and there are lots of them, so you can be very granular. All of the API permissions sit under the DataActions section so that a custom role would look something like this:

{
    "Name": "AKS Deployment Reader",
    "Description": "Lets you view all deployments in cluster/namespace.",
    "Actions": [],
    "NotActions": [],
    "DataActions": [
        "Microsoft.ContainerService/managedClusters/apps/deployments/read"
    ],
    "NotDataActions": [],
    "assignableScopes": [
        "/subscriptions/<YOUR SUBSCRIPTION ID>"
    ]
}

Again, you would assign these roles to users on the AKS object. Once done, users should connect and use the rights you have given them.

Privileged Identity Management

Just a quick note on Privileged Identity Management (PIM). If you want to enable Just in Time (JIT) access to these Kubernetes roles, you can do so with either approach but using a different method.

For Azure RBAC roles, it is probably easiest. As these are just Azure RBAC roles, you can use the standard PIM process to create a role that a user is eligible to elevate to and assign that user role. The user can then elevate when required.

For Kubernetes RBAC, you would need to be using Azure AD groups to assign the Kubernetes Role or ClusterRole. Assign the role to a group, but keep this group empty of users. You would then use the Privileged Access Groups feature to allow the user to elevate into the group when access is required. Note that Privileged Access Groups is currently in preview.