Example Terraform repo with GitHub Actions using federated Managed Identity and OpenID Connect (OIDC).
The federated identity credential creates a trust relationship between an application and an external identity provider (IdP).
You can then configure an external software workload to exchange a token from the external IdP for an access token from Microsoft identity platform.
Assumed a Bash environment with az, git and gh installed.
Check that you are logged into github.com using gh auth status. Not logged in? Use gh auth login.
Check that you are logged into Azure using az account show. Not logged in? Use az login. Wrong subscription? Use az account set.
Add fork step
git clone https://github.com/<yourGitHubID>/federated_managed_identitycd federated_managed_identitygitHubUser=$(git remote get-url origin | cut -f4 -d/)
gitHubRepo=$(git remote get-url origin | cut -f5 -d/)The following command will save defaults local to the current working directory for region and resource group.
az config set --local defaults.location="UK South" defaults.group=terraformThese are used by the Azure CLI when you run commands from this directory.
az group create --name "terraform"Create a user assigned managed identity
az identity create --name "terraform"
identityId=$(az identity show --name "terraform" --query id --output tsv)Assign the Contributor role at the subscription scope
az role assignment create \
--assignee "$(az identity show --name "terraform" --query principalId --output tsv)" \
--role "Contributor" \
--scope "/subscriptions/$(az account show --query id --output tsv)"Add the federated identity credential
az identity federated-credential create --name "terraform-github" --identity-name "terraform" \
--issuer 'https://token.actions.githubusercontent.com'\
--subject "repo:$gitHubUser/$gitHubRepo:ref:refs/heads/main"\
--audiences 'api://AzureADTokenExchange'Permitted subject claims for GitHub
repo:$gitHubUser/$gitHubRepo:environment:my-envrepo:$gitHubUser/$gitHubRepo:ref:refs/heads/my-branchrepo:$gitHubUser/$gitHubRepo:ref:refs/tags/my-tagrepo:$gitHubUser/$gitHubRepo:pull-request$environment:job_workflow_ref:$organisation/$gitHubRepo/.github/workflows/$workflow.yaml@refs/heads/main
Generate a predictable 8 character hash from the resource group's resource ID. This will be used to help the storage account FQDN to be globally unique.
groupid=$(az group show --name terraform --query id --output tsv)
hash=$(echo "$groupid" | sha256sum | cut -c1-8)az storage account create --name "terraform$hash" \
--identity-type UserAssigned --user-identity-id $identityId \
--sku Standard_RAGZRS \
--min-tls-version TLS1_2 --allow-blob-public-access falseaz storage container create --name tfstate \
--account-name terraform$hash --auth-mode loginstorageId=$(az storage account show --name "terraform$hash" --query id --output tsv)This is (close to) the format for the provider for service principals with OIDC and the backend when authenticating using OIDC:
cat <<EOT > terraform/backend.tf
terraform {
backend "azurerm" {
use_oidc = true
tenant_id = "$(az account show --query tenantId --output tsv)"
subscription_id = "$(az account show --query id --output tsv)"
resource_group_name = "terraform"
storage_account_name = "terraform$hash"
container_name = "tfstate"
key = "$gitHubRepo"
}
}
EOTExample backend.tf file:
terraform {
backend "azurerm" {
use_oidc = true
tenant_id = "3c584bbd-915f-4c70-9f2e-7217983f22f6"
subscription_id = "9b7a166a-267f-45a5-b480-7a04cfc1edf6"
resource_group_name = "terraform"
storage_account_name = "terraform66615a0f"
container_name = "tfstate"
key = "federated_managed_identity"
}
}gh secret set ARM_CLIENT_ID --body $(az identity show --name "terraform" --query clientId --output tsv)
gh secret set ARM_SUBSCRIPTION_ID --body $(az account show --query id --output tsv)
gh secret set ARM_TENANT_ID --body $(az identity show --name "terraform" --query tenantId --output tsv)
gh secret set ARM_BACKEND_RESOURCEGROUP --body $(az config get --local defaults.group --query value --output tsv --only-show-errors)
gh secret set ARM_BACKEND_STORAGEACCOUNT --body "terraform$hash"
Info on workload identity federation - includes managed applications as well as service principals - https://learn.microsoft.com/azure/active-directory/develop/workload-identity-federation
This is the page for appIds - https://learn.microsoft.com/azure/active-directory/develop/workload-identity-federation-create-trust
And THIS IS THE ONE for managed identity - https://learn.microsoft.com/azure/active-directory/develop/workload-identity-federation-create-trust-user-assigned-managed-identity
Needs Contributor or Managed Identity Contributor
Others:
- Official GitHub to Azure is just OpenId on appId and also Service Principal - https://learn.microsoft.com/azure/developer/github/connect-from-azure
- Limitations - https://learn.microsoft.com/azure/active-directory/develop/workload-identity-federation-considerations
- Permit IPs for GitHub Actions - to be added
- https://github.com/hashicorp/setup-terraform
- https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_oidc