Azure 58: AAD App Registrations via terraform-common
easy⏱ 5 mincourseazure
What the module provisions
One azuread_application (the app registration), one azuread_service_principal (the identity used at runtime), an application_identifier_uri derived from service + domain (e.g. api://cart-api.driveway.com), the requested app_roles, and federated credentials so the AKS workload identity can mint tokens without a client secret. All wrapped in consistent naming and tags.
module "cart-api-app" {
source = "git::...terraform-common//azuread/app-registration/application-domain?ref=main"
service_name = "cart-api"
environment = "prod"
domain = "driveway.com"
app_roles = ["Cart.Read", "Cart.ReadWrite"]
federated_identity = {
aks_oidc_issuer = data.azurerm_kubernetes_cluster.apps.oidc_issuer_url
namespace = "apps"
service_account = "cart-api"
}
}
Federated identity > client secret
The old way: app registration has a client_secret, store it in Key Vault, mount it into the pod, pray nobody copies it. The new way: federated credentials let the AKS workload identity exchange its OIDC token for an AAD token directly. No secret in Key Vault, no rotation, no leak. Lythia's module wires this for every new app registration; only legacy services still carry secrets.
App roles model permissions
App roles are typed strings (Cart.Read) that consuming APIs can require via roles claim. APIM's validate-jwt policy or Spring Boot's OAuth2ResourceServer checks the claim. App roles are far cleaner than mapping AAD groups for service-to-service auth: machine identities just get the role, no human-membership ambiguity.
Generate + validate the module call
Write buildAppRegistration({ service, env, domain, appRoles, federated }). Validate: service is lowercase-dashed, env in dev/test/uat/prod, domain ends with .com or .cloud, every appRole matches ^[A-Z][A-Za-z]+\.[A-Z][A-Za-z]+$ (e.g. Cart.Read). If federated is provided, require all three keys. Emit the HCL module "..." {} block as a string. Log one valid + one invalid registration.
Prefer Application.ReadWrite.OwnedBy over Application.ReadWrite.All
If the SPN that drives Terraform needs to manage app registrations, give it the OwnedBy permission — it can only edit registrations whose owner is itself. The All variant lets it edit any tenant app registration, which is over-broad. Configure ownership in the module so the Terraform SPN remains the owner of everything it creates.
Quiz: stale federated credentials after AKS upgrade
Q: After upgrading the AKS cluster, all pod-to-Entra calls start failing with AADSTS70021. Cause? A: The cluster's OIDC issuer URL changed during the upgrade. The federated credential pins to the previous issuer. Fix: re-run Terraform — the module re-reads data.azurerm_kubernetes_cluster.apps.oidc_issuer_url and updates the federated credential. This is why the module is data-source-driven, not literal.