Authenticating Spring Boot apps on AKS with MSI

Last week I was looking for the best solution to connect to Azure services for a few pods within Azure Kubernetes Service. These services are build with Spring Boot and my first idea was to only connect to Azure KeyVault. Along the way I discovered the awesomeness of Managed Service Identities and extended it to another Azure Service, Azure Storage.

Managed Service Identities: a short intro

To explain a bit about Managed Service Identities I prefer to point towards the Azure docs:

The point of Managed Service Identities or also called system-assigned Managed Identities is to provide you with an identity which is bound to the service. This means that the Managed Service Identity can be assigned permissions, but that after removal of the service, the Managed Service Identity and its permissions are also gone. I’m only gonna show you AKS and its Managed Service Identity functionality in action, from now on called: MSI.

Besides the Managed Service Identities we will also use user-assigned Managed Identities. This is also an Azure Managed Identity, created in Azure AD, but not assigned at creation time to a specific service and is a standalone Azure resource. One of these is assigned to our AKS Virtual Machine Scale Set and another one is used as the identity for our pod.

The outline

I’ll show you how to create a simple Spring Boot sample application in AKS. This app is build with Maven, containerized with Jib and finaly connecting with to the Azure resources with MSI credentials.

The list of required steps:

  1. Create an Azure Kubernetes Service cluster with MSI enabled
  2. Create an Azure Container Registry for your containers
  3. Create an Azure Key Vault to contain your secrets
  4. Create an Azure Storage Account tot contain your files
  5. Connect your Azure Container Registry with AKS
  6. Create an Managed Identity for your pod
  7. Install aad-pod-identity on your AKS cluster
  8. Provide permissions for your AKS MSI to manage your Azure Managed Identity
  9. Bind the Azure Identity to your pod with aad-pod-identity configuration
  10. Create an Spring Boot application and build it, push a container to your registry
  11. Test it!

Let’s go!

Tools:

The tools necessary to do this on your own. I’m working on Ubuntu, so all examples are based on that:

# Install az cli latest version
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# Install azul JDK and mvn 3.6.x
sudo apt-get install zulu-11 -y
sudo apt install maven -y
mvn -version

# install docker credential helper for acr/linux
curl -L https://aka.ms/acr/installaad/bash | /bin/bash

Step 1: AKS

First step along the way is to create an AKS environment which facilitates this. First of all, create AKS with MSI enabled. This is a recent feature for AKS:

az login

export CLUSTERNAME=testmsi
export RESOURCE_GROUP=aks-rg
export SUBS=<your subscription id>
az group create -n $RESOURCE_GROUP -l "West Europe"
az aks create -g $RESOURCE_GROUP -n $CLUSTERNAME --enable-managed-identity

Step 2: Key Vault

In the meanwhile, waiting for the cluster is not necessary. Let’s open up another shell and start creating the other components necessary for this experiment:

export CLUSTERNAME=testmsi
export RESOURCE_GROUP=aks-rg
export SUBS=<your subscription id>

# create an Azure Key Vault with a few secrets
az keyvault create -n $CLUSTERNAME -g $RESOURCE_GROUP
az keyvault secret set --name your-property-name \
  --vault-name $CLUSTERNAME --value youdidit135

Step 3: Container Registry

# create an Azure Container Registry
az acr create -n $CLUSTERNAME -g $RESOURCE_GROUP --sku Basic

Step 4: Storage

# create Blob Storage in an Azure Storage Account
az storage account create -n "${CLUSTERNAME}sa1" -g $RESOURCE_GROUP --sku Standard_LRS
az storage container create -n "blobs" --account-name "${CLUSTERNAME}sa1" --auth-mode login
cat <<EOT >> blobs.txt
This is your test file
EOT
az storage blob upload -f ./blobs.txt -c "blobs" -n "blobs.txt" --account-name "${CLUSTERNAME}sa1" 

export STORAGE_URI=$(az storage account show -n "${CLUSTERNAME}sa1" -g $RESOURCE_GROUP --query 'primaryEndpoints.blob' -o tsv)

Step 5: Connect AKS to your container registry

Wow, that was a lot to take in already. Let’s check first if AKS is already there. If not, take a small break and drink some water. Stay hydrated!)

If AKS is provisioned for you, we want to connect your Azure Container Registry to your AKS instance:

az aks update -n $CLUSTERNAME -g $RESOURCE_GROUP --attach-acr $CLUSTERNAME

Step 6: Create an identity for our pod

The pod will use an user-assiged Managed Identity. This identity is not bound to a service, but is visible within your resource group.

# make an identity for our pods
export POD_IDENTITY_NAME=demo-aad1
export NODE_RESOURCE_GROUP=$(az aks show -n $CLUSTERNAME -g $RESOURCE_GROUP \
  --query nodeResourceGroup -otsv)
export PRINCIPAL_ID=$(az identity create -n $POD_IDENTITY_NAME \
  -g $NODE_RESOURCE_GROUP --query 'principalId' -o tsv)

Our identity to use by the pod is created, but what about it’s permissions? And how to connect it to the actual pod? We add permissions to access the Key Vault and to read our blob storage:

# allow access to the key vault secrets for our pod identity
az role assignment create --role Reader --assignee $PRINCIPAL_ID \
  --scope /subscriptions/$SUBS/resourcegroups/$NODE_RESOURCE_GROUP
az keyvault set-policy -n $CLUSTERNAME -g $RESOURCE_GROUP \
  --object-id $PRINCIPAL_ID --secret-permissions get list

export STORAGE_ID=$(az storage account show -n "${CLUSTERNAME}sa1" -g $RESOURCE_GROUP --query 'id' -o tsv)

az role assignment create --role "Storage Blob Data Reader" --assignee $PRINCIPAL_ID \
  --scope $STORAGE_ID

Step 7: Prepare our AKS cluster with aad-pod-identity

We are ready to deploy aad-pod-identity. It might seem simple from your point of view, but figuring it out took me a short while. We want to use our MSI within the cluster to do some magic for our pods. To make this happen, we need pod identities. With a few simple commands we make this magic happen:

# deploy aad-pod-identity
az aks get-credentials -g $RESOURCE_GROUP -n $CLUSTERNAME
kubectl create -f https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment-rbac.yaml
NODE_RESOURCE_GROUP=$(az aks show -g $RESOURCE_GROUP -n $CLUSTERNAME \
  --query nodeResourceGroup -o tsv)

Step 8: Extend the permissions for our kubelet identity

# allow our kubelet identity to manage the identities
az role assignment create --role "Virtual Machine Contributor" \
  --assignee $KUBELET_IDENTITY_ID  \
  --scope /subscriptions/$SUBS/resourcegroups/$NODE_RESOURCE_GROUP
az role assignment create --role "Managed Identity Operator" \ 
  --assignee $KUBELET_IDENTITY_ID  \
  --scope /subscriptions/$SUBS/resourcegroups/$NODE_RESOURCE_GROUP

Step 9: Configure your cluster to use the Identity for your pod

# find values for our AKS pod identity configuration:
CLIENT_ID=$(az identity show -n $POD_IDENTITY_NAME -g ${NODE_RESOURCE_GROUP} | jq -r .clientId)
RESOURCE_ID=$(az identity show -n $POD_IDENTITY_NAME -g ${NODE_RESOURCE_GROUP} | jq -r .id)
echo "Pod Identity name = ${POD_IDENTITY_NAME}"
echo "Client id = ${CLIENT_ID}"
echo "Resource id = ${RESOURCE_ID}"

# apply pod identity configuration to AKS
cat <<EOT >> azureidentity.yml
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentity
metadata:
  name: $POD_IDENTITY_NAME
spec:
  type: 0 # Azure ID (1 is SP)
  ResourceID: $RESOURCE_ID
  ClientID: $CLIENT_ID
EOT
kubectl apply -f azureidentity.yml

# bind the pod identity to pods with label `aadpodidbinding: demo`:
cat <<EOT >> azureidentitybinding.yml
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentityBinding
metadata:
  name: $POD_IDENTITY_NAME-binding
spec: 
  AzureIdentity: $POD_IDENTITY_NAME
  Selector: "demo"
EOT
kubectl apply -f azureidentitybinding.yml

Step 10: Create an application and build it, push a container to your registry

We’ve done a lot already, but we still lack an application which reads the secrets from the Azure Key Vault. Therefore, we use an application which only reads the secret and writes it to the logs. Don’t do that in production:)'

The Spring Boot application to use can be found here. When we build this application, Jib takes care of pushing an image to your container registry directly. So, let’s do it!

# clone the application
git clone https://github.com/aristosvo/azure-keyvault-secrets-spring-boot.git

# login to your Azure Container Registry
az acr login -n $CLUSTERNAME

# build the application and push it with Jib to your container registry
cd ./azure-keyvault-secrets-spring-boot
mvn clean install

Step 11: Last but not least, deploy and see the magic happen!

We are almost there. Only 2 things left:

# deploying the application
kubectl apply -f application.yml

# check the logs!
kubectl log -l app=spring-boot-demo | grep "yourSecretPropertyName"

The result should contain a message that your secret property had value ‘youdidit135’ and that one blob is found, ‘blobs’.

Have a nice day!

aristosvo

comments powered by Disqus