Introduction
In production & enterprise-grade setups an AKS cluster gets usually configured to authenticate users against Azure Entra ID and perform authorziation decisions based on the Kubernetes RBAC model.
This makes sense since Entra ID is usually the central identity provider with on-premises Active Directory, while the Kubernetes API still manages authorization decisions.
But have you ever wondered how the Azure Entra ID integration works and why the additional helper binary called kubelogin
is required?
In this post, we'll look at Kubernetes and its authentication mechanism and see how it's connected and integrated into Entra ID. Let's get started!
How Kubernetes authentication works without Entra ID
As you might know, Kubernetes has no objects representing normal user accounts, unlike it has for service accounts.
Instead, any HTTP request hitting the Kubernetes API presenting a valid certificate signed by the cluster's CA is considered authenticated.
In this case, the user's identity is derived from the common name field in the subject
of the certificate.
After authentication, the Kubernetes RBAC sub-system takes care of authorization decisions and passes the request on to the admission controllers.
Running kubectl auth whoami
, will return the following
ATTRIBUTE VALUE
Username masterclient
Groups [system:masters system:authenticated]
The depicted user certificate from above has been taken and decoded from the kubeconfig file. It gets pulled and installed by Azure CLI, amongst a private key and the CA certificate, when you execute az aks get-credentials
.
The certificate-authority-data
key holds the cluster certificate, while the client-certificate-data
and client-key-data
keys hold the user's certificate and corresponding private key.
Okay, this is all nice and swell 🧐, but how does Entra ID and kubelogin fit in here?
The thing with kubectl and kubelogin
Regarding authentication and authorization, OAuth 2.0 and OpenID Connect are the go-to protocols and defacto standards on the Internet.
However, these protocols are not natively understood by kubectl
, which can only work with certificates and bearer tokens.
To be more precise, kubectl can attach a bearer token to its requests towards the Kubernetes API, but it can't fetch or refresh any bearer tokens.
⚠️ Please be aware that there are three projects on GitHub with the name kubelogin
🤯 This article refers to Microsoft implementation found here.
So this is why the kubelogin binary is required to execute any OAuth flows and pass the retrieved tokens on to kubectl
. The following diagram gives a high-level overview of the process.
After executing az aks get-credentials
on an Entra ID integrated cluster, your kubeconfig will carry a key called exec
in a user
object. This entry defines which client-go credential plugin to call and which arguments should be used.
apiVersion: v1
clusters: ...
contexts: ...
current-context: your-aks-cluster
kind: Config
preferences: {}
users:
- name: clusterUser_your-resource-group_your-aks-cluster
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- get-token
- --environment
- AzurePublicCloud
- --server-id
- 6dae42f8-4368-4678-94ff-3960e28e3630
- --client-id
- 80faf920-1908-4b52-b5ef-a8e7bedfc67a
- --tenant-id
- <your-tenant-id>
- --login
- interactive
command: kubelogin
env: null
installHint: ...
provideClusterInfo: false
From the example above, the entire kubelogin
, including its arguments, looks as follows.
kubelogin get-token \
--environment AzurePublicCloud \
--server-id 6dae42f8-4368-4678-94ff-3960e28e3630 \
--client-id 80faf920-1908-4b52-b5ef-a8e7bedfc67a \
--tenant-id <your-tenant-id> \
--login interactive
These two Entra ID app registrations, client and server app, are managed by the AKS resource provider for you and configured when you execute:
az aks create -g <group> -n <cluster> --enable-aad --aad-admin-group-object-ids <id> [--aad-tenant-id <id>]
Okay, so kubelogin
covers the client-side functionality of the authentication process. But how do things work on the Kubernetes API side, and what does the big picture look like?
Kubelogin, Entra ID & OpenID Connect
Before we get to the big picture, let's have a look the following diagram, that depicts the relation between the components and how they relate to each other in OAuth 2.0 terminology.
The user (or resource owner) delegates the rights to access it's identifying data to the client, kubectl, and kubelogin.
Further, the client must be registered with Entra ID (the authorization server), which authorizes the token requests.
Last but not least, AKS, the resource server, has a trust relation to Azure Entra ID. This is required so that AKS can validate the received bearer tokens against Entra ID.
The entire picture
Now that we better understand the components, we can merge the diagrams and add more details.
Let's walk through each of the steps.
Steps 1, 2
The user executes kubectl
which in turn invokes kubelogin
with the server-id
, client-id
and tenant-id
.
Steps 3 - 7
The browser opens, and the user is asked to authenticate. The credentials are verified, and an authorization code is requested by communicating with the Entra IDs authorization endpoint, which is then passed back to kubelogin
.
Steps 8, 9, 10
Kubelogin exchanges the received authorization code for a bearer token by communicating with the token endpoint. Then, the token returns to kubectl
, which attaches it to the users' HTTP request to the Kubernetes API.
GET /api/v1/namespaces/test/pods
Authorization: Bearer eyJ0eXAiOiJKV7QiLCJhbGciOiJSUzI1N...
Step 11
At this point, the Kubernetes API must check that the token signature is valid.
Therefore, it discovers the JWK URI endpoint by using a well-known URL and fetches the public key published at the jwks_uri
key.
https://sts.windows.net/<tenant-id>/.well-known/openid-configuration
Then, it decodes the token and validates if...
- ... the intended recipient match (
aud
audience claim) - ... the token time window is valid (
exp
expiration time,nbf
not before) - ... the client ID matches (
appid
)
Step 12
After all authentication conditions are met, the user is considered authenticated, and the request is passed on to the kube-api's authorization decision...
Conclusion
Let's wrap up 📑
- Kubernetes has no user objects, and they can't be created by the API.
- The authentication mechanism uses OpenID Connect.
kubectl
does not implement any OAuth flows.- A client-go exec plugin called
kubelogin
is required, which implements OAuth. - There are three different
kubelogin
projects on GitHub; don't get confused. - The client and server IDs are static and reused across multiple (all?) AKS deployments that are Entra ID integrated.
- The client and server app registrations in Entra ID are managed for you by Microsoft.
That's it for today. I hope you enjoyed reading this article. Always happy to receive feedback! Happy hacking 👨🏽💻🤓