Quick Start
- 1. Complete the installation guide
- 2. Export environment variables
- 3. Create an Azure Key Vault and secret
- 4. Create an AAD application and grant permissions to access the secret
- 5. Create a Kubernetes service account
- 6. Establish federated identity credential between the AAD application and the service account issuer & subject
- 7. Deploy workload
- 8. Cleanup
In this tutorial, we will cover the basics of how to use the webhook to acquire an Azure AD token to access a secret in an Azure Key Vault.
Before we get started, ensure the following:
- You are logged in with the Azure CLI as a user.
- If you are logged in with a Service Principal, ensure that it has the correct API permissions enabled.
- Your logged in account must have sufficient permissions to create applications and service principals in Azure AD.
1. Complete the installation guide
Installation guide. At this point, you should have already:
- installed the mutating admission webhook
- obtained your cluster’s OIDC issuer URL
- [optional] installed the Azure AD Workload Identity CLI
2. Export environment variables
# environment variables for the Azure Key Vault resource
export KEYVAULT_NAME="azwi-kv-$(openssl rand -hex 2)"
export KEYVAULT_SECRET_NAME="my-secret"
export RESOURCE_GROUP="azwi-quickstart-$(openssl rand -hex 2)"
export LOCATION="westus2"
# environment variables for the AAD application
export APPLICATION_NAME="<your application name>"
# environment variables for the Kubernetes service account & federated identity credential
export SERVICE_ACCOUNT_NAMESPACE="default"
export SERVICE_ACCOUNT_NAME="workload-identity-sa"
export SERVICE_ACCOUNT_ISSUER="<your service account issuer url>" # see section 1.1 on how to get the service account issuer url
3. Create an Azure Key Vault and secret
Create an Azure resource group:
az group create --name "${RESOURCE_GROUP}" --location "${LOCATION}"
Create an Azure Key Vault:
az keyvault create --resource-group "${RESOURCE_GROUP}" \
--location "${LOCATION}" \
--name "${KEYVAULT_NAME}"
Create a secret:
az keyvault secret set --vault-name "${KEYVAULT_NAME}" \
--name "${KEYVAULT_SECRET_NAME}" \
--value "Hello\!"
4. Create an AAD application and grant permissions to access the secret
Azure Workload Identity CLI
azwi serviceaccount create phase app --aad-application-name "${APPLICATION_NAME}"
Output
INFO[0000] No subscription provided, using selected subscription from Azure CLI: REDACTED
INFO[0005] [aad-application] created an AAD application clientID=REDACTED name=azwi-test objectID=REDACTED
WARN[0005] --service-principal-name not specified, falling back to AAD application name
INFO[0005] [aad-application] created service principal clientID=REDACTED name=azwi-test objectID=REDACTED
Azure CLI
az ad sp create-for-rbac --name "${APPLICATION_NAME}"
Set access policy for the AAD application to access the keyvault secret:
export APPLICATION_CLIENT_ID="$(az ad sp list --display-name "${APPLICATION_NAME}" --query '[0].appId' -otsv)"
az keyvault set-policy --name "${KEYVAULT_NAME}" \
--secret-permissions get \
--spn "${APPLICATION_CLIENT_ID}"
5. Create a Kubernetes service account
Create a Kubernetes service account and annotate it with the client ID of the AAD application we created in step 4:
Azure Workload Identity CLI
azwi serviceaccount create phase sa \
--aad-application-name "${APPLICATION_NAME}" \
--service-account-namespace "${SERVICE_ACCOUNT_NAMESPACE}" \
--service-account-name "${SERVICE_ACCOUNT_NAME}"
Output
INFO[0000] No subscription provided, using selected subscription from Azure CLI: REDACTED
INFO[0002] [service-account] created Kubernetes service account name=workload-identity-sa namespace=default
kubectl
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
azure.workload.identity/client-id: ${APPLICATION_CLIENT_ID}
labels:
azure.workload.identity/use: "true"
name: ${SERVICE_ACCOUNT_NAME}
namespace: ${SERVICE_ACCOUNT_NAMESPACE}
EOF
Output
serviceaccount/workload-identity-sa created
If the AAD application is not in the same tenant as the default tenant defined during installation, then annotate the service account with the application tenant ID:
kubectl annotate sa ${SERVICE_ACCOUNT_NAME} -n ${SERVICE_ACCOUNT_NAMESPACE} azure.workload.identity/tenant-id="${APPLICATION_TENANT_ID}" --overwrite
6. Establish federated identity credential between the AAD application and the service account issuer & subject
Azure Workload Identity CLI
azwi serviceaccount create phase federated-identity \
--aad-application-name "${APPLICATION_NAME}" \
--service-account-namespace "${SERVICE_ACCOUNT_NAMESPACE}" \
--service-account-name "${SERVICE_ACCOUNT_NAME}" \
--service-account-issuer-url "${SERVICE_ACCOUNT_ISSUER}"
Output
INFO[0000] No subscription provided, using selected subscription from Azure CLI: REDACTED
INFO[0032] [federated-identity] added federated credential objectID=REDACTED subject="system:serviceaccount:default:workload-identity-sa"
Azure CLI
Login to Azure Cloud Shell and run the following commands:
# Get the object ID of the AAD application
export APPLICATION_OBJECT_ID="$(az ad app show --id ${APPLICATION_CLIENT_ID} --query id -otsv)"
Add the federated identity credential:
cat <<EOF > body.json
{
"name": "kubernetes-federated-credential",
"issuer": "${SERVICE_ACCOUNT_ISSUER}",
"subject": "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}",
"description": "Kubernetes service account federated credential",
"audiences": [
"api://AzureADTokenExchange"
]
}
EOF
az rest --method POST --uri "https://graph.microsoft.com/beta/applications/${APPLICATION_OBJECT_ID}/federatedIdentityCredentials" --body @body.json
7. Deploy workload
Deploy a pod that references the service account created in the last step:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: quick-start
namespace: ${SERVICE_ACCOUNT_NAMESPACE}
spec:
serviceAccountName: ${SERVICE_ACCOUNT_NAME}
containers:
- image: ghcr.io/azure/azure-workload-identity/msal-go
name: oidc
env:
- name: KEYVAULT_NAME
value: ${KEYVAULT_NAME}
- name: SECRET_NAME
value: ${KEYVAULT_SECRET_NAME}
nodeSelector:
kubernetes.io/os: linux
EOF
Feel free to swap the msal-go example image above with a list of language-specific examples we provide.
To check whether all properties are injected properly by the webhook:
kubectl describe pod quick-start
Output
You can verify the following injected properties in the output:
Environment variable | Description |
---|---|
AZURE_AUTHORITY_HOST | The Azure Active Directory (AAD) endpoint. |
AZURE_CLIENT_ID | The client ID of the AAD application. |
AZURE_TENANT_ID | The tenant ID of the registered AAD application. |
AZURE_FEDERATED_TOKEN_FILE | The path of the projected service account token file. |
Volume mount | Description |
---|---|
/var/run/secrets/azure/tokens/azure-identity-token | The path of the projected service account token file. |
Volume | Description |
---|---|
azure-identity-token | The projected service account volume. |
Name: quick-start
Namespace: default
Priority: 0
Node: k8s-agentpool1-38097163-vmss000002/10.240.0.34
Start Time: Wed, 13 Oct 2021 15:49:25 -0700
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.240.0.55
IPs:
IP: 10.240.0.55
Containers:
oidc:
Container ID: containerd://f425e89eef9aa3a62eb51a3daa5af8c06d8a59baa79c4e4dbb1887aea2647048
Image: ghcr.io/azure/azure-workload-identity/msal-go:latest
Image ID: ghcr.io/azure/azure-workload-identity/msal-go@sha256:84421aeea707ce66ade0891d9fcd3bb3f7bbd5dd3f810caced0acd315dcf8751
Port: <none>
Host Port: <none>
State: Running
Started: Wed, 13 Oct 2021 15:49:29 -0700
Ready: True
Restart Count: 0
Environment:
KEYVAULT_NAME: ${KEYVAULT_NAME}
SECRET_NAME: ${KEYVAULT_SECRET_NAME}
AZURE_AUTHORITY_HOST: (Injected by the webhook)
AZURE_CLIENT_ID: (Injected by the webhook)
AZURE_TENANT_ID: (Injected by the webhook)
AZURE_FEDERATED_TOKEN_FILE: (Injected by the webhook)
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-844ns (ro)
/var/run/secrets/azure/tokens from azure-identity-token (ro) (Injected by the webhook)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-844ns:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
azure-identity-token: (Injected by the webhook)
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 86400
QoS Class: BestEffort
Node-Selectors: kubernetes.io/os=linux
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 19s default-scheduler Successfully assigned oidc/quick-start to k8s-agentpool1-38097163-vmss000002
Normal Pulling 18s kubelet Pulling image "ghcr.io/azure/azure-workload-identity/msal-go:latest"
Normal Pulled 16s kubelet Successfully pulled image "ghcr.io/azure/azure-workload-identity/msal-go:latest" in 1.987165801s
Normal Created 15s kubelet Created container oidc
Normal Started 15s kubelet Started container oidc
To verify that pod is able to get a token and access the secret from the Key Vault:
kubectl logs quick-start
Output
If successful, the log output would be similar to the following output:
I1013 22:49:29.872708 1 main.go:30] "successfully got secret" secret="Hello!"
8. Cleanup
kubectl delete pod quick-start
kubectl delete sa "${SERVICE_ACCOUNT_NAME}" --namespace "${SERVICE_ACCOUNT_NAMESPACE}"
az group delete --name "${RESOURCE_GROUP}"
az ad sp delete --id "${APPLICATION_CLIENT_ID}"