Open zmoog opened 5 months ago
After some reading and a few failed attempts, the document Deploy and configure workload identity on an Azure Kubernetes Service (AKS) cluster seems the best resource I was able to get.
For this walkthrough, I'm using an existing cluster named mbranca-sdh-4160
:
export RESOURCE_GROUP="mbranca-sdh-4160"
export CLUSTER_NAME="mbranca-sdh-4160"
export LOCATION="eastus2"
export SERVICE_ACCOUNT_NAMESPACE="default"
export SERVICE_ACCOUNT_NAME="workload-identity-sa"
export SUBSCRIPTION="$(az account show --query id --output tsv)"
export USER_ASSIGNED_IDENTITY_NAME="myIdentity"
export FEDERATED_IDENTITY_CREDENTIAL_NAME="myFedIdentity"
Since I already have a cluster, I'll set it up enabling OIDC and workload identity:
az aks update \
-g "${RESOURCE_GROUP}" \
-n "${CLUSTER_NAME}" \
--enable-oidc-issuer \
--enable-workload-identity
Now that OIDC is enabled, we can collect the OIDC Issuer URL and store it as environment variable:
export AKS_OIDC_ISSUER="$(az aks show --resource-group "${RESOURCE_GROUP}" --name "${CLUSTER_NAME}" --query "oidcIssuerProfile.issuerUrl" -otsv)"
From the source document:
Use the Azure CLI az account set command to set a specific subscription to be the current active subscription. Then use the az identity create command to create a managed identity.
# set the default subscription
az account set --subscription "${SUBSCRIPTION}"
az identity create --name "${USER_ASSIGNED_IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}" --location "${LOCATION}" --subscription "${SUBSCRIPTION}"
# (optional) if you need to delete one
az identity delete --name "${USER_ASSIGNED_IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}" --subscription "${SUBSCRIPTION}"
Create a Kubernetes service account and annotate it with the client ID of the managed identity created in the previous step. Use the az aks get-credentials command and replace the values for the cluster name and the resource group name.
az aks get-credentials -n "${CLUSTER_NAME}" -g "${RESOURCE_GROUP}"
Copy and paste the following multi-line input in the Azure CLI.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
azure.workload.identity/client-id: "${USER_ASSIGNED_CLIENT_ID}"
name: "${SERVICE_ACCOUNT_NAME}"
namespace: "${SERVICE_ACCOUNT_NAMESPACE}"
EOF
Use the az identity federated-credential create command to create the federated identity credential between the managed identity, the service account issuer, and the subject.
az identity federated-credential create \
--name ${FEDERATED_IDENTITY_CREDENTIAL_NAME} \
--identity-name "${USER_ASSIGNED_IDENTITY_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--issuer "${AKS_OIDC_ISSUER}" \
--subject system:serviceaccount:"${SERVICE_ACCOUNT_NAMESPACE}":"${SERVICE_ACCOUNT_NAME}" \
--audience api://AzureADTokenExchange
For this test, I created a trivial "application" that count the VMs available on a subscription.
Here's the source code:
package main
import (
"context"
"fmt"
"net/http"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"k8s.io/klog/v2"
)
func main() {
subscriptionId := os.Getenv("SUBSCRIPTION_ID")
credential, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
klog.Fatal(err)
}
client, err := armcompute.NewVirtualMachinesClient(subscriptionId, credential, nil)
if err != nil {
klog.Fatal(err)
}
pager := client.NewListAllPager(nil)
for pager.More() {
page, err := pager.NextPage(context.Background())
if err != nil {
klog.Fatal(err)
}
fmt.Printf("Found %d VMs\n", len(page.Value))
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, Kubernetes!")
})
err = http.ListenAndServe(":8080", nil)
if err != nil {
klog.Fatal(err)
}
}
Here is the Dockerfile to build an image:
FROM golang:1.21 as builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myapp
FROM alpine:3.14
WORKDIR /app
COPY --from=builder /app/myapp /app/
EXPOSE 8080
CMD ["/app/myapp"]
And finally, the K8s deployment file:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
namespace: default
labels:
azure.workload.identity/use: "true"
app: myapp
spec:
serviceAccountName: workload-identity-sa
containers:
- name: myapp
image: zmoog/myapp:latest
env:
- name: SUBSCRIPTION_ID
value: "<put your subscription ID here>"
I build and pushed the image to Docker Hub:
docker build -t zmoog/myapp:latest .
docker push zmoog/myapp:latest
The workload identity infrastructure is ready, but we must give myIdentity
permission to read the VM information.
I'm using Azure Portal for this step, but I want to come back later and replace this section with a CLI command.
On Azure Portal:
Now, the identity "myIdentity" is authorized to perform the action the app needs.
As a final step, I will deploy and run the app on k8s.
$ k apply -f deployment.yaml
deployment.apps/myapp-deployment created
$ k get pods
NAME READY STATUS RESTARTS AGE
myapp-deployment-785ff98547-tmxn9 1/1 Running 0 14s
$ k logs myapp-deployment-785ff98547-tmxn9
Found 7 VMs
I have an AKS cluster on Azure. I want to write a Go application that lists the VM on a subscription without using credentials.
Azure offers an authentication mechanism called workload identity that seems a good fit.
Let's see how it works.