Azure Active Directory pod-managed identities(AAD Pod Identity v1) enable pods to securely access cloud resources using Managed Identity on AKS, greatly reducing the attack surface of using cloud credentials for application workload. It’s not a surprise that it has been one of the most frequently used features for production AKS users.
However, there are certain limitations imposed by technical architecture that hinders the GA of Pod Identity v1.
Limitations
- A maximum of 200 pod identities are allowed for a cluster.
- A maximum of 200 pod identity exceptions are allowed for a cluster.
- Pod-managed identities are available on Linux node pools only.
We recently announced a new service called AAD Workload Identity which will be the next generation of Pod Identity. It is completely redesigned to remove the limitations of v1. Technical details of v1 are well described here, however, v1 is not a topic of discussion in this article.
NOTE: Azure Workload Identity installation won’t be covered. You can find the details on the Github page. https://azure.github.io/azure-workload-identity/docs/installation.html. We will focus on technical details of Workload Identity, and Spring Boot implementation.
Azure Workload Identity(Preview)
Azure Workload Identity Github page is at https://azure.github.io/azure-workload-identity/docs/introduction.html. I have copied & pasted the advantages over v1 from the link below.
- Removes the scale and performance issues that existed for identity assignment
- Supports Kubernetes clusters hosted in any cloud or on-premises
- Supports both Linux and Windows workloads
- Removes the need for Custom Resource Definitions and pods that intercept Instance Metadata Service (IMDS) traffic
- Avoids the complication and error-prone installation steps such as cluster role assignment from the previous iteration.
Authentication Flow
The flow starts with “Service Account Token Volume Projection” which projects the service account token within the pod. This happens behind the scene by Mutating Admission Webhook. Once the pod is deployed, you will be able to see the Service Account Token mounted in the pod.
- name: azure-identity-token
projected:
defaultMode: 420
sources:
- serviceAccountToken:
audience: api://AzureADTokenExchange
expirationSeconds: 3600
path: azure-identity-token
The audience is set to api://AzureADTokenExchange
that is the default value for this federated credential to work. The actual token projected in the pod looks like this.
{
"aud": [
"api://AzureADTokenExchange"
],
"exp": 1646808223,
"iat": 1646804623,
"iss": "https://oidc.prod-aks.azure.com/28562482-e0a6-4012-ae58-89522724eed9/",
"kubernetes.io": {
"namespace": "default",
"pod": {
"name": "workloadidentity-blob",
"uid": "79e461db-64be-4653-a9f7-dd45ced5ed4f"
},
"serviceaccount": {
"name": "workload-identity-sa",
"uid": "2c455bf3-c2d4-458e-9c41-94332f2e60ef"
}
},
"nbf": 1646804623,
"sub": "system:serviceaccount:default:workload-identity-sa"
}
This is the token that will be presented to Azure AD so that a basic setup is required from the Azure AD side. You need to register an app and set up the federated credential. It can be done by either CLI or Azure portal.
Workload Identity flow starts with a Service Account token so that it requires a service account with specific labels and annotations. If you use CLI, it does necessary labeling for you. Otherwise, you should create a service account with labels and annotations as below.
Name: workload-identity-sa
Namespace: default
Labels: azure.workload.identity/use=true
Annotations: azure.workload.identity/client-id: 4ac69285-cca0-450e-b29a-450c1e17615d
azure.workload.identity/tenant-id: 72f988bf-86f1-41af-91ab-2d7cd011db47
Image pull secrets: <none>
Mountable secrets: workload-identity-sa-token-mwpc8
Tokens: workload-identity-sa-token-mwpc8
Events: <none>
The pod should specifically use this service account.
apiVersion: v1
kind: Pod
metadata:
name: workloadidentity-blob
labels:
app: workloadidentity-blob
spec:
serviceAccountName: workload-identity-sa
containers:
- name: workloadidentity-blob
image: eggboy/workloadidentity-blob:0.0.1
imagePullPolicy: Always
env:
- name: BLOB_ACCOUNT_NAME
value: ""
- name: BLOB_CONTAINER_NAME
value: ""
restartPolicy: Always
After creating a pod, ‘kubectl describe’ to see what changes are made by Admission Webhook. The most notable changes other than service account token volume is environment variables.
AZURE_CLIENT_ID: 4ac69285-cca0-450e-b29a-450c1e17615d
AZURE_TENANT_ID: 72f988bf-86f1-41af-91ab-2d7cd011db47
AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token
AZURE_AUTHORITY_HOST: https://login.microsoftonline.com/
These variables will be used to construct a Credential that will be supplied with other Azure resources SDK like Blob Storage, Keyvauly, etc.
Now It’s time to look at Spring Boot implementation. There are two dependencies to be added manually in the pom.xml at the time of writing.
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.11.2</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.4.5</version>
</dependency>
Make sure to use the msal4j version above 1.11.2, it will fail otherwise.
public class FederatedCredential implements TokenCredential {
@Value("${AZURE_FEDERATED_TOKEN_FILE}")
private String AZURE_FEDERATED_TOKEN_FILE;
@Value("${AZURE_AUTHORITY_HOST}")
private String AZURE_AUTHORITY_HOST;
@Value("${AZURE_TENANT_ID}")
private String AZURE_TENANT_ID;
@Value("${AZURE_CLIENT_ID}")
private String AZURE_CLIENT_ID;
@Override
public Mono<AccessToken> getToken(TokenRequestContext tokenRequestContext) {
String clientAssertion = null;
try {
clientAssertion = Files.readString(Paths.get(AZURE_FEDERATED_TOKEN_FILE));
}
catch (IOException e) {
log.error("Error getting AZURE_FEDERATED_TOKEN_FILE", e);
}
IClientCredential credential = ClientCredentialFactory.createFromClientAssertion(clientAssertion);
StringBuilder authority = new StringBuilder();
authority.append(AZURE_AUTHORITY_HOST);
authority.append(AZURE_TENANT_ID);
try {
ConfidentialClientApplication app = ConfidentialClientApplication
.builder(AZURE_CLIENT_ID, credential).authority(authority.toString()).build();
Set<String> scopes = tokenRequestContext.getScopes().stream().collect(Collectors.toSet());
ClientCredentialParameters parameters = ClientCredentialParameters.builder(scopes).build();
IAuthenticationResult result = app.acquireToken(parameters).join();
return Mono.just(
new AccessToken(result.accessToken(), result.expiresOnDate().toInstant().atOffset(ZoneOffset.UTC)));
}
catch (Exception e) {
log.error("Error creating client application.", e);
}
return Mono.empty();
}
}
BlobContainerClient
@Bean
public BlobContainerClient blobContainerClient() {
String accountName = env.getProperty("BLOB_ACCOUNT_NAME");
String containerName = env.getProperty("BLOB_CONTAINER_NAME");
String endpoint = String.format(Locale.ROOT, "https://%s.blob.core.windows.net", accountName);
BlobServiceClient storageClient = new BlobServiceClientBuilder().endpoint(endpoint)
.credential(federatedCredential).buildClient();
return storageClient.getBlobContainerClient(containerName);
}
Once setup is done properly, check the logs to see if there are any exceptions. Azure Identity SDK is saying it can successfully acquire the access token from AAD.
2022-03-09 08:33:42.116 INFO 1 --- [ parallel-1] c.a.c.implementation.AccessTokenCache : Acquired a new access token.
Conclusion
Workload Identity is the next generation of Pod Identity based on OIDC and Kubernetes native implementation. It is still in early-stage with rough edges, and most importantly it’s missing Managed Identity support at the moment, but the fundamental design of the new implementation looks fantastic and future-proof. I will write another article once Managed Identity support is available. Stay tuned!
The sample code is at https://github.com/eggboy/AKS/tree/main/WorkloadIdentity/storage-test
If you like my article, please leave some claps here or maybe even start following me. Thanks!