Using Workload Idenity to Access Azure Blob Storage
In this post we'll show the steps needed to create an AKS cluster, enabled with Azure Workload Identity and then we'll build a sample dotnet app that writes files to Azure Blob Storage
Workload Identity to Blob Storage
The following walkthrough shows how you can using Azure Workload Identity with the AKS Workload Identity add-on along with MSAL to access an Azure Blob Storage Account.
Cluster Creation
Now lets create the AKS cluster with the OIDC Issuer and Workload Identity add-on enabled.
RG=WorkloadIdentityRG
LOC=eastus
CLUSTER_NAME=wilab
UNIQUE_ID=$CLUSTER_NAME$RANDOM
ACR_NAME=$UNIQUE_ID
STORAGE_ACCT_NAME=griffdemo
# Create the resource group
az group create -g $RG -l $LOC
# Create the cluster with the OIDC Issuer and Workload Identity enabled
az aks create -g $RG -n $CLUSTER_NAME \
--node-count 1 \
--enable-oidc-issuer \
--enable-workload-identity \
--generate-ssh-keys
# Get the cluster credentials
az aks get-credentials -g $RG -n $CLUSTER_NAME
Set up the identity
In order to federate a managed identity with a Kubernetes Service Account we need to get the AKS OIDC Issure URL, create the Managed Identity and Service Account and then create the federation.
# Get the OIDC Issuer URL
export AKS_OIDC_ISSUER="$(az aks show -n $CLUSTER_NAME -g $RG --query "oidcIssuerProfile.issuerUrl" -otsv)"
# Create the managed identity
az identity create --name wi-demo-identity --resource-group $RG --location $LOC
# Get identity client ID
export USER_ASSIGNED_CLIENT_ID=$(az identity show --resource-group $RG --name wi-demo-identity --query 'clientId' -o tsv)
# Create a service account to federate with the managed identity
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
azure.workload.identity/client-id: ${USER_ASSIGNED_CLIENT_ID}
labels:
azure.workload.identity/use: "true"
name: wi-demo-sa
namespace: default
EOF
# Federate the identity
az identity federated-credential create \
--name wi-demo-federated-id \
--identity-name wi-demo-identity \
--resource-group $RG \
--issuer ${AKS_OIDC_ISSUER} \
--subject system:serviceaccount:default:wi-demo-sa
Create the Blob Storage Account
# Create a blob storage account
az storage account create \
--name $STORAGE_ACCT_NAME \
--resource-group $RG \
--location $LOC \
--sku Standard_LRS \
--encryption-services blob
# Get the resource ID of the storage account
STORAGE_ACCT_ID=$(az storage account show -g $RG -n $STORAGE_ACCT_NAME --query id -o tsv)
# Get the current signed in user ID
CURRENT_USER=$(az ad signed-in-user show --query id -o tsv)
# Grant the current user contributor rights for testing
az role assignment create \
--role "Storage Blob Data Contributor" \
--assignee $CURRENT_USER \
--scope "${STORAGE_ACCT_ID}"
# Grant the managed identity contributor rights
az role assignment create \
--role "Storage Blob Data Contributor" \
--assignee $USER_ASSIGNED_CLIENT_ID \
--scope "${STORAGE_ACCT_ID}"
# Create a storage account container with login auth mode enabled
az storage container create --account-name $STORAGE_ACCT_NAME --name data --auth-mode login
Create the sample app
# Create and test a new console app
dotnet new console -n blob-console-app
cd blob-console-app
dotnet run
# Add the Key Vault and Azure Identity Packages
dotnet add package Azure.Storage.Blobs
dotnet add package Azure.Identity
Edit the app as follows:
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using System;
using System.IO;
using Azure.Identity;
class Program
{
static void Main(string[] args)
{
// Get Storage Account Name
string? storageAcctName = Environment.GetEnvironmentVariable("STORAGE_ACCT_NAME");;
// Get the Storage Container Name
string? containerName = Environment.GetEnvironmentVariable("CONTAINER_NAME");;
// Check values for null or empty
if (string.IsNullOrEmpty(storageAcctName)||string.IsNullOrEmpty(containerName))
{
Console.WriteLine("Storage Account or Container Name are null or empty");
Environment.Exit(0);
}
while (true)
{
MainAsync(storageAcctName,containerName).Wait();
System.Threading.Thread.Sleep(5000);
}
}
static async Task MainAsync(string storageAcctName, string containerName)
{
var blobServiceClient = new BlobServiceClient(
new Uri(String.Format("https://{0}.blob.core.windows.net",storageAcctName)),
new DefaultAzureCredential());
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
// Create a local file in the ./data/ directory for uploading and downloading
string localPath = "data";
Directory.CreateDirectory(localPath);
string fileName = Guid.NewGuid().ToString() + ".txt";
string localFilePath = Path.Combine(localPath, fileName);
// Write text to the file
await File.WriteAllTextAsync(localFilePath, "Hello, World!");
// Get a reference to a blob
BlobClient blobClient = containerClient.GetBlobClient(fileName);
Console.WriteLine("Uploading to Blob storage as blob:\n\t {0}\n", blobClient.Uri);
// Upload data from the local file
await blobClient.UploadAsync(localFilePath, true);
}
}
Create a new Dockerfile with the following:
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "blob-console-app.dll"]
Build the image. I’ll create an Azure Container Registry and build there, and then link that ACR to my AKS cluster.
# Create the ACR
az acr create -g $RG -n $ACR_NAME --sku Standard
# Build the image
az acr build -t wi-blob-test -r $ACR_NAME .
# Link the ACR to the AKS cluster
az aks update -g $RG -n $CLUSTER_NAME --attach-acr $ACR_NAME
Now deploy a pod that runs our blob storage app using the service account identity.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: wi-blob-test
namespace: default
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: wi-demo-sa
containers:
- image: ${ACR_NAME}.azurecr.io/wi-blob-test
name: wi-blob-test
env:
- name: STORAGE_ACCT_NAME
value: ${STORAGE_ACCT_NAME}
- name: CONTAINER_NAME
value: data
nodeSelector:
kubernetes.io/os: linux
EOF
# Check the pod logs
kubectl logs -f wi-blob-test
# Sample Output
Uploading to Blob storage as blob:
https://griffdemo.blob.core.windows.net/data/quickstart3efa9a81-9672-4617-a6ff-f11fb93d7c84.txt
Uploading to Blob storage as blob:
https://griffdemo.blob.core.windows.net/data/quickstart23968d6b-80c5-4c82-8bcf-860fa00edbd3.txt
Uploading to Blob storage as blob:
https://griffdemo.blob.core.windows.net/data/quickstart0e20e7ef-c3ba-4fd3-a3d5-c27579d2ba96.txt
Conclusion
Congrats! You should now have a working pod that uses MSAL along with a Kubernetes Service Account federated to an Azure Managed Identity to access Azure Blob Storage.