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_identity
cd federated_managed_identity
gitHubUser=$(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=terraform
These 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-env
repo:$gitHubUser/$gitHubRepo:ref:refs/heads/my-branch
repo:$gitHubUser/$gitHubRepo:ref:refs/tags/my-tag
repo:$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 false
az storage container create --name tfstate \
--account-name terraform$hash --auth-mode login
storageId=$(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"
}
}
EOT
Example 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