AKS Pod Connecting to PostgreSQL Database using Workload Identity
Introduction
Securely connecting Kubernetes pods to Azure services like PostgreSQL is essential for modern cloud applications. Traditional approaches using connection strings with usernames and passwords pose security risks and management overhead. Azure Kubernetes Service (AKS) workload identity provides a secure, passwordless authentication mechanism that enables pods to authenticate to Azure services using Azure Active Directory (Azure AD) identities.
This article demonstrates how to configure AKS workload identity to enable pods to connect to Azure Database for PostgreSQL Flexible Server using managed identities, eliminating the need for storing database credentials in the application code or Kubernetes secrets.
Key Concepts
What is an OIDC Issuer?
An OpenID Connect (OIDC) issuer is a service that issues and validates identity tokens. In the context of AKS, the OIDC issuer provides a way for Kubernetes service accounts to be authenticated by Azure AD through federated identity credentials. For Example: AKS cluster's OIDC issuer enables pods to prove their identity to Azure AD without storing database passwords in the application code.
What are Federated Credentials?
Federated credentials establish a trust relationship between an Azure AD application or managed identity and an external identity provider. They define which external identities can impersonate the Azure AD identity. For Example: When the pod needs to connect to PostgreSQL, federated credentials allow the Kubernetes service account to "become" the managed identity, which can then authenticate to the PostgreSQL server using Azure AD authentication.
What is a Managed Identity?
A managed identity is an Azure AD identity that is automatically managed by Azure. It eliminates the need for developers to manage credentials by providing an identity for applications to use when connecting to resources that support Azure AD authentication. For Example: The managed identity serves as the application's database user in Azure AD. Instead of creating a traditional PostgreSQL user with a password, you assign the managed identity appropriate database permissions.
What is AKS Workload Identity?
AKS workload identity is a feature that enables Kubernetes pods to authenticate to Azure services using Azure AD identities. It uses OIDC federation to establish trust between the Kubernetes cluster and Azure AD, allowing pods to obtain Azure AD tokens using their Kubernetes service account. For Example: When a pod with workload identity starts up, it automatically receives an Azure AD token that can be used to authenticate to PostgreSQL as the configured managed identity user.
What is Workload Identity Federation?
Workload identity federation allows external identity providers (like Kubernetes) to exchange their tokens for Azure AD tokens without storing long-lived credentials. This enables secure access to Azure resources from workloads running outside of Azure. For Example: This eliminates the need to store PostgreSQL passwords in Kubernetes secrets. the pod's service account token is exchanged for an Azure AD token that PostgreSQL recognizes for authentication.
What is Kubernetes Service Accounts?
A Kubernetes service account provides an identity for processes that run in a pod. When pods need to communicate with the Kubernetes API server or external services, they use service accounts for authentication. In the context of workload identity, service accounts are linked to Azure AD identities through annotations, enabling pods to authenticate to Azure services without storing credentials. For Example: The service account acts as the pod's database identity card. By annotating it with workload identity configuration, you're telling AKS "when pods use this service account, let them authenticate to PostgreSQL as this specific managed identity user."
Prerequisites
- A well-configured AKS cluster with Kubernetes version 1.22 or later
- Azure CLI installed and configured
- kubectl command-line tool installed
- A well-configured Azure Database for PostgreSQL Flexible Server with databases
- Azure AD permissions to create and manage managed identities
- Contributor or Owner role on the Azure subscription
- Basic understanding of Kubernetes concepts (pods, service accounts, deployments)
- Docker image of the application ready to deploy
Architecture Overview
The following diagram illustrates the high-level architecture and authentication flow for AKS workload identity with PostgreSQL:
Authentication Flow Details
-
Token Request: When the application pod starts, it uses the configured service account to request an identity token from the AKS OIDC issuer.
-
Token Exchange: The workload identity system automatically exchanges the Kubernetes service account token for an Azure AD access token using the federated credential configuration established between the AKS cluster and Azure AD.
-
Token Validation: Azure AD validates the token by:
- Verifying it came from the trusted OIDC issuer (AKS cluster)
- Confirming the service account subject matches the federated credential
-
Issuing an Azure AD token for the managed identity
-
Database Authentication: The application uses the Azure AD token for authentication when connecting to PostgreSQL, which validates it against Azure AD and allows the connection.
Key Components
- OIDC Issuer: Provides the trust anchor between AKS and Azure AD
- Service Account: Links the pod to the managed identity through annotations
- Managed Identity: Acts as the database user in Azure AD (no password needed)
- Federated Credential: Establishes the trust relationship between the service account and managed identity
- PostgreSQL AD Admin: Configures the database to accept Azure AD authentication
This architecture eliminates credential management by using cryptographic tokens and trust relationships instead of storing passwords or connection strings in the application or Kubernetes secrets.
Authentication Sequence Diagram
The following sequence diagram illustrates the detailed step-by-step authentication flow when a pod connects to PostgreSQL using workload identity:
Sequence Flow Explanation:
- Pod Initialization: The application pod starts with the configured service account that has workload identity annotations (
azure.workload.identity/use: "true"label triggers the workload identity webhook to inject environment variables and projected service account token volumes into the pod) - Token Request: Pod requests a service account token from the AKS OIDC issuer
- Service Account Token: OIDC issuer returns a JWT token proving the pod's identity
- Token Exchange: The workload identity system initiates token exchange with Azure AD using the federated credential and projected token
- Federated Validation: Azure AD validates the federated credential configuration
- Trust Confirmation: Managed identity confirms the trust relationship exists
- Azure AD Token: Azure AD returns an access token for the managed identity
- Database Connection: Pod connects to PostgreSQL using the Azure AD token for authentication
- Token Validation: PostgreSQL validates the token with Azure AD
- Authentication Success: Azure AD confirms token validity
- Connection Established: Database connection is established with managed identity permissions
- Query Execution: Pod can now execute SQL queries as the managed identity user
- Results: PostgreSQL returns query results to the authenticated pod
Key Point: The azure.workload.identity/use: "true" label is essential - it signals the workload identity mutating webhook to automatically inject the necessary environment variables (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE) and mount the projected service account token, enabling automatic token exchange through federated credentials without manual configuration.
This sequence eliminates traditional password-based authentication and provides secure, token-based access to PostgreSQL.
Step 1: Enable Workload Identity on AKS
The first step is configuring the AKS cluster to support workload identity authentication. By default, AKS clusters don't have this feature enabled, which means pods cannot authenticate to Azure services using managed identities.
Enabling workload identity requires both the workload identity addon and an OIDC issuer. The OIDC issuer establishes the trust relationship between Kubernetes and Azure AD, acting as the identity provider that Azure AD can verify and trust. Think of this as installing a "passport system" for the cluster - just like you need a passport office to issue passports for international travel, the cluster needs an OIDC issuer to provide identity tokens that Azure AD recognizes for PostgreSQL authentication.
Using Azure CLI
# Enable workload identity on existing cluster
az aks update \
--resource-group myResourceGroup \
--name myAKSCluster \
--enable-workload-identity \
--enable-oidc-issuer
Using Terraform
resource "azurerm_kubernetes_cluster" "aks" {
name = "myAKSCluster"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
workload_identity_enabled = true
oidc_issuer_enabled = true
# ... other configuration
}
Verification Commands
# Verify workload identity is enabled
az aks show -g myResourceGroup -n myAKSCluster --query "securityProfile.workloadIdentity.enabled" -o tsv
# Expected output: true
# Verify OIDC issuer is enabled and get the URL
az aks show -g myResourceGroup -n myAKSCluster --query "oidcIssuerProfile.issuerUrl" -o tsv
# Expected output: https://oidc.prod-aks.azure.com/[tenant-id]/[unique-id]/
# Check cluster addon status
az aks show -g myResourceGroup -n myAKSCluster --query "addonProfiles.azureKeyvaultSecretsProvider.enabled" -o tsv
Step 2: Create User-Assigned Managed Identity
The next step involves creating the Azure AD identity that will serve as the application's database user in PostgreSQL, replacing traditional username/password authentication entirely. PostgreSQL with Azure AD authentication requires an Azure AD principal to connect, and the managed identity fulfills this requirement.
This is essentially creating a "service account" in Azure AD specifically for the application. Just like you might create a dedicated database user for an application, you're creating a dedicated Azure AD identity. The key difference is this identity doesn't have a password - it uses cryptographic tokens for authentication, eliminating the need to store or manage any credentials in the application or Kubernetes secrets.
# Create a user-assigned managed identity
az identity create \
--name myAppIdentity \
--resource-group myResourceGroup \
--location myLocation
# Get the identity client ID
export USER_ASSIGNED_CLIENT_ID="$(az identity show --name myAppIdentity --resource-group myResourceGroup --query 'clientId' -o tsv)"
Verification Commands
# Verify the managed identity was created successfully
az identity show --name myAppIdentity --resource-group myResourceGroup --query '{name:name, clientId:clientId, principalId:principalId, resourceGroup:resourceGroup}' -o table
# Expected output: Table showing identity details
# List all managed identities in the resource group
az identity list --resource-group myResourceGroup --query '[].{name:name, clientId:clientId}' -o table
# Verify environment variable is set
echo "CLIENT_ID: $USER_ASSIGNED_CLIENT_ID"
# Expected output: CLIENT_ID: [guid-format-client-id]
Step 3: Create Kubernetes Service Account
The Kubernetes service account acts as the bridge between the pods and the Azure managed identity. Pods need a way to signal that they want to use workload identity for authentication, and the service account provides this mechanism through specific annotations that tell the workload identity system which Azure identity to use.
Think of this as giving the pod a passport that identifies it as belonging to a specific country (managed identity) and having the right to travel to certain destinations (database access). When the pod starts, Kubernetes sees this passport and automatically provides the necessary authentication tokens.
Create the service account with workload identity annotations:
# postgresql-service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: postgresql-workload-identity-sa
namespace: default
annotations:
azure.workload.identity/client-id: <USER_ASSIGNED_CLIENT_ID>
labels:
azure.workload.identity/use: "true"
Apply the service account:
Verification Commands
# Verify the service account was created with correct annotations
kubectl get serviceaccount postgresql-workload-identity-sa -n default -o yaml
# Expected output: ServiceAccount with azure.workload.identity annotations
# Check service account annotations specifically
kubectl get serviceaccount postgresql-workload-identity-sa -n default -o jsonpath='{.metadata.annotations}' | jq
# Expected output: JSON showing azure.workload.identity/client-id and azure.workload.identity/use annotations
# List all service accounts in the namespace
kubectl get serviceaccounts -n default
# Expected output: List including postgresql-workload-identity-sa
# Verify the client ID annotation matches your managed identity
kubectl get serviceaccount postgresql-workload-identity-sa -n default -o jsonpath='{.metadata.annotations.azure\.workload\.identity/client-id}'
echo ""
echo "Expected: $USER_ASSIGNED_CLIENT_ID"
Step 4: Create Federated Identity Credential
Federated credentials establish the trust relationship between the Kubernetes service account and the Azure managed identity - this is the core of workload identity federation. Azure AD needs explicit permission to trust tokens from the AKS cluster, and the federated credential provides this by saying "if a token comes from this specific OIDC issuer (cluster) and claims to be this specific service account, then treat it as this managed identity."
This is like registering the country's passport system with international border control. You're telling Azure AD "when someone presents a passport from our Kubernetes cluster claiming to be the 'postgresql-workload-identity-sa' service account from the default namespace, trust them as if they're our managed identity citizen."
The OIDC issuer URL is a unique identifier that Azure AD uses to verify identity tokens are coming from the specific AKS cluster. Each AKS cluster has a unique issuer URL that serves as its "digital fingerprint" in the identity federation process. Think of this URL as the official address of the cluster's passport office - when the pod presents an identity token to PostgreSQL, Azure AD checks this URL to confirm the token came from the trusted cluster before allowing database access.
Using Azure CLI
# Get the OIDC issuer URL from the AKS cluster
export AKS_OIDC_ISSUER="$(az aks show -n myAKSCluster -g myResourceGroup --query "oidcIssuerProfile.issuerUrl" -o tsv)"
echo $AKS_OIDC_ISSUER
# Create federated identity credential
az identity federated-credential create \
--name myAppFederatedCredential \
--identity-name myAppIdentity \
--resource-group myResourceGroup \
--issuer $AKS_OIDC_ISSUER \
--subject system:serviceaccount:default:postgresql-workload-identity-sa
Using Terraform
# Get the OIDC issuer URL from the AKS cluster
data "azurerm_kubernetes_cluster" "aks" {
name = "myAKSCluster"
resource_group_name = "myResourceGroup"
}
# Reference the existing managed identity
data "azurerm_user_assigned_identity" "app_identity" {
name = "myAppIdentity"
resource_group_name = "myResourceGroup"
}
# Create federated identity credential
resource "azurerm_federated_identity_credential" "app_federated_credential" {
name = "myAppFederatedCredential"
resource_group_name = data.azurerm_user_assigned_identity.app_identity.resource_group_name
audience = ["api://AzureADTokenExchange"]
issuer = data.azurerm_kubernetes_cluster.aks.oidc_issuer_url
parent_id = data.azurerm_user_assigned_identity.app_identity.id
subject = "system:serviceaccount:default:postgresql-workload-identity-sa"
}
Note: The audience is set to ["api://AzureADTokenExchange"] which is the standard audience for Azure AD workload identity federation. The subject follows the Kubernetes service account format: system:serviceaccount:<namespace>:<service-account-name>.
Verification Commands
# Verify the federated credential was created successfully
az identity federated-credential show \
--name myAppFederatedCredential \
--identity-name myAppIdentity \
--resource-group myResourceGroup \
--query '{name:name, issuer:issuer, subject:subject, audience:audiences}' -o table
# List all federated credentials for the managed identity
az identity federated-credential list \
--identity-name myAppIdentity \
--resource-group myResourceGroup \
--query '[].{name:name, subject:subject, issuer:issuer}' -o table
# Verify the OIDC issuer URL matches
echo "OIDC Issuer from cluster: $AKS_OIDC_ISSUER"
az identity federated-credential show \
--name myAppFederatedCredential \
--identity-name myAppIdentity \
--resource-group myResourceGroup \
--query 'issuer' -o tsv
# Expected output: Both URLs should match
# Verify the subject format is correct
az identity federated-credential show \
--name myAppFederatedCredential \
--identity-name myAppIdentity \
--resource-group myResourceGroup \
--query 'subject' -o tsv
# Expected output: system:serviceaccount:default:postgresql-workload-identity-sa
Step 5: Configure PostgreSQL Access
Now you need to grant the managed identity access to the PostgreSQL server. Even though you have a managed identity, PostgreSQL doesn't automatically know about it. You must register the managed identity as an Azure AD administrator on the PostgreSQL server, which enables it to create database connections using Azure AD authentication instead of traditional passwords.
This is essentially adding the service account to the database's user directory. You're telling PostgreSQL "this Azure AD identity is authorized to connect, and when it does, treat it as a legitimate database user with appropriate permissions."
# Grant the managed identity access to PostgreSQL
az postgres flexible-server ad-admin set \
--resource-group myResourceGroup \
--server-name myPostgreSQLServer \
--object-id $(az identity show --name myAppIdentity --resource-group myResourceGroup --query 'principalId' -o tsv) \
--display-name myAppIdentity \
--type User
Note: After configuring the managed identity as AD admin, you may need to connect to PostgreSQL and create specific database users and grant appropriate permissions:
-- Connect as AD admin and create user for managed identity
CREATE USER "myAppIdentity" WITH LOGIN;
GRANT CONNECT ON DATABASE mydatabase TO "myAppIdentity";
GRANT USAGE ON SCHEMA public TO "myAppIdentity";
-- Grant specific permissions based on application needs
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO "myAppIdentity";
Verification Commands
# Verify the managed identity is set as AD admin
az postgres flexible-server ad-admin show \
--resource-group myResourceGroup \
--server-name myPostgreSQLServer \
--query '{login:login, sid:sid, tenantId:tenantId}' -o table
# Check if Azure AD authentication is enabled
az postgres flexible-server show \
--resource-group myResourceGroup \
--name myPostgreSQLServer \
--query 'authConfig.activeDirectoryAuth' -o tsv
# Expected output: Enabled
# Verify the managed identity object ID matches
MANAGED_IDENTITY_PRINCIPAL_ID=$(az identity show --name myAppIdentity --resource-group myResourceGroup --query 'principalId' -o tsv)
echo "Managed Identity Principal ID: $MANAGED_IDENTITY_PRINCIPAL_ID"
az postgres flexible-server ad-admin show \
--resource-group myResourceGroup \
--server-name myPostgreSQLServer \
--query 'sid' -o tsv
# Expected output: Should match the Principal ID above
# Test PostgreSQL connectivity (requires psql client)
# Replace <your-tenant-id> with actual tenant ID
# az postgres flexible-server execute \
# --name myPostgreSQLServer \
# --resource-group myResourceGroup \
# --admin-user myAppIdentity \
# --database-name mydatabase \
# --querytext "SELECT current_user;"
Step 6: Deploy the Application
This is where everything comes together. You need to configure the application deployment to use the workload identity service account and provide the necessary environment variables for Azure AD authentication to PostgreSQL. The pods must be explicitly configured to use the service account you created, and the application code needs to know the Azure client ID and tenant ID to properly authenticate with Azure AD before connecting to PostgreSQL.
Notice that you're providing the managed identity name as the database user, but no password - the workload identity system with federated credentials handles authentication automatically. You're essentially telling Kubernetes "when you run this pod, give it the identity (service account) we set up, and the federated credential system will handle token exchange to prove its identity to PostgreSQL."
Key Workload Identity Configuration Points:
# api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dotnet-api
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: dotnet-api
template:
metadata:
labels:
app: dotnet-api
# 🔑 REQUIRED: This label tells the workload identity webhook to inject environment variables
azure.workload.identity/use: "true"
spec:
# 🔑 REQUIRED: Reference the workload identity service account
serviceAccountName: postgresql-workload-identity-sa
containers:
- name: api-app
image: your-registry.azurecr.io/myapp-api:latest
ports:
- containerPort: 8080
env:
# 🔑 APPLICATION-SPECIFIC: Connection string uses managed identity name as username, NO PASSWORD
- name: DATABASE_URL
value: "Host=myPostgreSQLServer.postgres.database.azure.com;Database=mydatabase;Username=myAppIdentity;SSL Mode=Require;"
# Note: AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_FEDERATED_TOKEN_FILE are automatically
# injected by the workload identity webhook when azure.workload.identity/use: "true" label is present
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
Critical Configuration Changes:
serviceAccountName: postgresql-workload-identity-sa- Links the pod to the workload identity service account- Automatic Environment Variable Injection - The workload identity webhook automatically injects
AZURE_CLIENT_ID,AZURE_TENANT_ID, andAZURE_FEDERATED_TOKEN_FILEwhen the pod has theazure.workload.identity/use: "true"label - Connection String - Uses the managed identity name (
myAppIdentity) as the username with no password specified
Important: The azure.workload.identity/use: "true" label is required in two places:
1. Service Account: Identifies the service account as workload identity enabled
2. Pod Template (in Deployment): Tells the workload identity mutating webhook which pods to inject configuration into
The webhook looks for this label on the pod template to:
- Inject environment variables (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE)
- Mount the projected service account token at /var/run/secrets/azure/tokens/azure-identity-token
- Configure the pod for automatic token exchange
The workload identity system with federated credentials automatically handles token exchange and injects the necessary configuration into the pod, eliminating the need for password-based authentication.
Verification Commands
# Apply the deployment
kubectl apply -f api-deployment.yaml
# Verify the deployment was created successfully
kubectl get deployment dotnet-api -n default -o wide
# Expected output: Deployment with READY status
# Check if pods are running with the correct service account
kubectl get pods -n default -l app=dotnet-api -o jsonpath='{.items[*].spec.serviceAccountName}'
# Expected output: postgresql-workload-identity-sa
# Check pod events for any issues
kubectl describe pods -n default -l app=dotnet-api
# Verify workload identity webhook injected the necessary volumes and environment variables
kubectl get pods -n default -l app=dotnet-api -o yaml | grep -A 10 -B 10 "azure-workload-identity"
# Check if the pod has the projected service account token
kubectl exec -it deployment/dotnet-api -n default -- ls -la /var/run/secrets/azure/tokens/
# Verify injected environment variables (automatically added by webhook)
kubectl exec -it deployment/dotnet-api -n default -- env | grep AZURE
Step 7: Verification
Finally, you need to validate that the entire workload identity chain is functioning correctly and the pods can successfully authenticate to PostgreSQL without credentials. Since workload identity involves multiple components working together (OIDC issuer, service accounts, federated credentials, managed identity, PostgreSQL AD admin), verification helps identify which component might be misconfigured if authentication fails.
Think of this as testing the passport system. You're checking that: 1) the passport is valid and recognized (token acquisition), 2) border control accepts the passport (Azure AD trusts the token), and 3) you can actually enter the country and access what you need (PostgreSQL connection).
# Check if workload identity is working
kubectl logs deployment/dotnet-api -n default
# Test connection from within the pod
kubectl exec -it deployment/dotnet-api -n default -- /bin/bash
# Inside the pod, test Azure AD token acquisition
curl -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://ossrdbms-aad.database.windows.net/"
Additional Verification Commands
# Check overall workload identity integration
kubectl get pods -n default -l app=dotnet-api -o jsonpath='{.items[0].metadata.labels.azure\.workload\.identity/use}'
# Expected output: true
# Verify the webhook mutations were applied
kubectl get mutatingwebhookconfiguration azure-wi-webhook-mutating-webhook-configuration -o yaml
# Check application logs for authentication success/failure
kubectl logs deployment/dotnet-api -n default --tail=50
# Verify end-to-end connectivity (if your app has a health endpoint)
kubectl port-forward deployment/dotnet-api 8080:8080 -n default &
sleep 5
curl -f http://localhost:8080/health || echo "Health check failed"
pkill -f "kubectl port-forward"
Conclusion
AKS workload identity with federated credentials provides a secure, scalable solution for connecting Kubernetes pods to Azure Database for PostgreSQL without managing credentials. By leveraging OIDC federation, managed identities, and federated identity credentials, you can eliminate password-based authentication while maintaining fine-grained access control.
This approach significantly improves security by removing the need to store database passwords in application code or Kubernetes secrets, while providing seamless authentication through Azure AD integration and automatic token exchange via federated credentials.
References
- Workload Identity Overview
- Workload Identity Federation
- Azure AD Workload Identity
- Azure AD Workload Identity
- OIDC on AKS
- Workload Identity on AKS
- Azure Samples - Spring Boot Application
- Azure Samples - Spring Boot Application
- Spring configuration for passwordless authentication
- Configure AD authentication with PostgreSQL
- PostgreSQL Authentication with Managed Identity
- A great article about workload identity federation
- Connect your Kubernetes application to your database without any credentials (and securely)

