The Microsoft 365 provider supports workload identity federation, which allows Kubernetes pods to authenticate to Microsoft 365 services without storing client secrets. This provides a more secure approach for containerized applications running in Kubernetes.
Running Terraform in Kubernetes is a specialized use case with specific benefits and challenges. This authentication method is most appropriate for GitOps workflows, internal developer platforms, or custom Kubernetes operators that use Terraform for infrastructure management.
How Workload Identity Works
Workload identity federation creates a trust relationship between:
A Kubernetes service account
A Microsoft Entra ID application
Instead of using long-lived secrets, the Kubernetes service account token is exchanged for an Azure access token through the OIDC protocol.
Benefits
Eliminates the need to manage secrets in Kubernetes
Reduces the risk of credential leakage
Aligns with zero trust security principles
Prerequisites
A Kubernetes cluster with workload identity configured
Permissions to create/modify Kubernetes service accounts
Permissions to create/modify Microsoft Entra ID applications
GitOps Workflows ArgoCD, Flux, or custom controllers to manage infrastructure
Developer Platforms Self-service infrastructure provisioning for development teams
CI/CD Pipelines Running Terraform as Kubernetes jobs in your CI/CD pipeline
Custom Operators Kubernetes operators that provision resources using Terraform
Setup
Using Azure CLI
Using Terraform
Create App Registration
# Set variables
TENANT_ID = "00000000-0000-0000-0000-000000000000"
APP_NAME = "terraform-m365-provider"
# Create app registration
APP_ID = $( az ad app create --display-name $APP_NAME --query appId -o tsv )
APP_OBJECT_ID = $( az ad app show --id $APP_ID --query id -o tsv )
# Create service principal
az ad sp create --id $APP_ID
# Grant API permissions
az ad app permission add \
--id $APP_ID \
--api 00000003-0000-0000-c000-000000000000 \
--api-permissions 9241abd9-d0e6-425a-bd4f-47ba86e767a4=Role
# Grant admin consent
az ad app permission admin-consent --id $APP_ID
Configure Kubernetes Service Account
# Create namespace
kubectl create namespace terraform-m365
# Create service account
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: terraform-m365-sa
namespace: terraform-m365
annotations:
azure.workload.identity/client-id: $APP_ID
EOF
Configure Federated Identity
# Get Kubernetes OIDC issuer
SERVICE_ACCOUNT_ISSUER = "$( kubectl get --raw /.well-known/openid-configuration | jq -r '.issuer')"
# Create federated credential
az ad app federated-credential create \
--id $APP_OBJECT_ID \
--parameters "{ \" name \" : \" kubernetes-federated-credential \" , \" issuer \" : \" $SERVICE_ACCOUNT_ISSUER \" , \" subject \" : \" system:serviceaccount:terraform-m365:terraform-m365-sa \" , \" audiences \" :[ \" api://AzureADTokenExchange \" ]}"
provider "azuread" {}
provider "kubernetes" {
config_path = "~/.kube/config"
}
# Get Kubernetes cluster info
data "kubernetes_cluster_info" "current" {}
locals {
oidc_issuer = data . kubernetes_cluster_info . current . oidc_issuer_url
}
# Create app registration
resource "azuread_application" "terraform_m365" {
display_name = "terraform-m365-provider"
}
# Create service principal
resource "azuread_service_principal" "terraform_m365" {
application_id = azuread_application . terraform_m365 . application_id
}
# Add API permissions
resource "azuread_application_api_permission" "graph_permissions" {
application_object_id = azuread_application . terraform_m365 . object_id
api_id = "00000003-0000-0000-c000-000000000000"
api_permissions {
id = "9241abd9-d0e6-425a-bd4f-47ba86e767a4"
type = "Role"
}
}
# Create Kubernetes namespace
resource "kubernetes_namespace" "terraform_m365" {
metadata {
name = "terraform-m365"
}
}
# Create Kubernetes service account
resource "kubernetes_service_account" "terraform_m365" {
metadata {
name = "terraform-m365-sa"
namespace = kubernetes_namespace . terraform_m365 . metadata [ 0 ] . name
annotations = {
"azure.workload.identity/client-id" = azuread_application.terraform_m365.application_id
}
}
}
# Add federated identity credential
resource "azuread_application_federated_identity_credential" "kubernetes" {
application_object_id = azuread_application . terraform_m365 . object_id
display_name = "kubernetes-federated-credential"
audiences = [ "api://AzureADTokenExchange" ]
issuer = local . oidc_issuer
subject = "system:serviceaccount: ${ kubernetes_namespace . terraform_m365 . metadata [ 0 ] . name } : ${ kubernetes_service_account . terraform_m365 . metadata [ 0 ] . name } "
}
Example Kubernetes Job:
apiVersion : batch/v1
kind : Job
metadata :
name : terraform-apply
namespace : terraform-m365
spec :
template :
metadata :
labels :
azure.workload.identity/use : "true"
spec :
serviceAccountName : terraform-m365-sa
containers :
- name : terraform
image : hashicorp/terraform:latest
command :
- /bin/sh
- -c
- |
cd /workspace
terraform init
terraform apply -auto-approve
env :
- name : M365_TENANT_ID
value : "00000000-0000-0000-0000-000000000000"
- name : M365_AUTH_METHOD
value : "workload_identity"
- name : M365_CLIENT_ID
value : "00000000-0000-0000-0000-000000000000"
volumeMounts :
- name : terraform-code
mountPath : /workspace
resources :
requests :
cpu : "500m"
memory : "512Mi"
limits :
cpu : "1"
memory : "1Gi"
volumes :
- name : terraform-code
configMap :
name : terraform-code
restartPolicy : Never
backoffLimit : 2
Provider Configuration
export M365_TENANT_ID = "00000000-0000-0000-0000-000000000000"
export M365_AUTH_METHOD = "workload_identity"
export M365_CLIENT_ID = "00000000-0000-0000-0000-000000000000"
export AZURE_FEDERATED_TOKEN_FILE = "/var/run/secrets/azure/tokens/azure-identity-token"
provider "microsoft365" {
auth_method = "workload_identity"
}
provider "microsoft365" {
auth_method = "workload_identity"
tenant_id = "00000000-0000-0000-0000-000000000000"
entra_id_options = {
client_id = "00000000-0000-0000-0000-000000000000"
federated_token_file_path = "/var/run/secrets/azure/tokens/azure-identity-token"
}
}
Security Considerations
Security Best Practices
The service account token file is automatically mounted in pods
Tokens are short-lived and automatically rotated by Kubernetes
Configure RBAC to limit which pods can use the service account
Configure conditional access policies in Azure
Enable audit logging for both Kubernetes and Microsoft Entra ID
Troubleshooting
Ensure the pod has the correct service account and the token file is mounted correctly.
Verify the federated credential is configured correctly with matching issuer and subject.
Ensure you’ve granted admin consent for the required Microsoft Graph permissions.
Verify the OIDC issuer URL in your federated credential matches your Kubernetes cluster’s issuer.
Pod not using workload identity
Check that the pod has the azure.workload.identity/use: "true" label.