Published
- 4 min read
Azure Federated Identity Guide: Terraform & GitHub Actions
I recently needed to transition from authenticating via Service Principal with a client secret to using OpenID Connect for Terraform actions within a few GitHub Actions workflows. This post is to showcase what I needed to change as there was not a single source of this information to perform this update.
TL;DR
With an existing Terraform GitHub Action workflow in place these are the key changes:
- Create Federated Identity Credentials for your Service Principal in Azure
- Add Azure Login action to workflow
- Add
id-token: write
to permissions - Ensure environment variable
ARM_USE_OIDC: true
is used for Terraform actions
Overview of current setup
Action secrets and variables
These are the current secrets configured for the production
environment:
- AZURE_ENTRA_ID_CLIENT_ID
- AZURE_ENTRA_ID_SUBSCRIPTION_ID
- AZURE_ENTRA_ID_TENANT_ID
- AZURE_ENTRA_ID_CLIENT_SECRET
GitHub Actions workflow snippet
This is a snippet from the full workflow showcasing the usage of the above secrets with the Terraform init action:
- name: Terraform Init
id: init
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_ENTRA_ID_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_ENTRA_ID_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_ENTRA_ID_TENANT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.AZURE_ENTRA_ID_CLIENT_SECRET }}
run: terraform init
How to create Federated Identity Credentials
I will walk through 3 different ways of creating the required credentials:
- Azure CLI
- Terraform
- Azure Portal
Thereafter I will describe the configuration and GitHub Actions workflow code to use this authentication flow.
Parameters overview
Below are the parameters with their descriptions to get a better understanding:
- issuer:
https://token.actions.githubusercontent.com
The URL of the external identity provider (Limit of 600 characters). The combination of ‘issuer’ and ‘subject’ must be unique for any given application object. - subject: This value is used to establish a connection between your GitHub Actions workflow and Microsoft Entra ID. (Limit of 600 characters) -
repo:{Organization}/{Repository}:{Entity}
. Entity would beenvironment:nameOfYourEnvironment
- audience: This value is used to establish a connection between your GitHub Actions workflow and Microsoft Entra ID. This value should be
api://AzureADTokenExchange
when using the GitHub Action for Azure Login. (Limit of 600 characters)
Using Azure CLI
Retrieve your App registration id
Using the Azure CLI you would need your application registration id which can be retrieved with:
APP_REG_NAME="app-reg-name"
APP_REG_ID=$(az ad app list --display-name $APP_REG_NAME --query "[0].appId" -o tsv)
Create json parameter values
You need to create json file with the required parameters, credentials.json:
{
"name": "federated-identity-credentials-name",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:Your-Organistaion/Your-Repository:environment:Your-Environment",
"description": "Federated Identity Credentials for GitHub Actions to deploy Azure resources using Terraform",
"audiences": ["api://AzureADTokenExchange"]
}
Exectue create
az ad app federated-credential create --id $APP_REG_ID --parameters credential.json
Using terraform
If you are using terraform to manage your application registrations, you can add an additional resource to create the Federated Identity Credentials.
resource "azuread_application_federated_identity_credential" "fed_creds" {
application_id = azuread_application.vmss_app.id
display_name = "federated-identity-credentials-name"
description = "Federated Identity Credentials for GitHub Actions to deploy Azure resources using Terraform"
audiences = ["api://AzureADTokenExchange"]
issuer = "https://token.actions.githubusercontent.com"
subject = "repo:Your-Organistaion/Your-Repository:environment:Your-Environment"
}
Using the Azure portal
Following these steps:
- Navigate to https://portal.azure.com
- Select Microsoft Entra ID
- Select App registration blade within left panel
- Locate you App Registration and select it (dives in resource view)
- Select Certificates & secrets within left panel
- Select Federated credentials tab
- Select ‘Add credential’
- Select scenario of ‘GitHub Actions deploying Azure resources’
- Add details within each field
Clean up
- Remove the client secret from the App Registration.
- Remove secret AZURE_ENTRA_ID_CLIENT_SECRET within the GitHub repository settings.
Transition your GitHub Actions workflow
Add Azure login action
You would need to add a new action:
- name: Azure login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_ENTRA_ID_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_ENTRA_ID_SUBSCRIPTION_ID }}
subscription-id: ${{ secrets.AZURE_ENTRA_ID_TENANT_ID }}
Make sure to update the job permissions with:
permissions:
id-token: write
Otherwise you will land up with the following error Error: Please make sure to give write permissions to id-token in the workflow.
Update your Terraform actions
Remove all instances of ARM_CLIENT_SECRET
environment variable. Introduce new environment variable ARM_USE_OIDC: true
, thus your task becomes:
- name: Terraform Init
id: init
env:
client-id: ${{ secrets.AZURE_ENTRA_ID_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_ENTRA_ID_SUBSCRIPTION_ID }}
subscription-id: ${{ secrets.AZURE_ENTRA_ID_TENANT_ID }}
ARM_USE_OIDC: true
Full GitHub Actions workflow
name: Terraform CI
on:
pull_request:
branches: main
workflow_dispatch:
jobs:
terraform:
runs-on: ubuntu-latest
environment: production
permissions:
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Azure login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_ENTRA_ID_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_AD_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_ENTRA_ID_TENANT_ID }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.8.0
- name: Terraform fmt
id: fmt
run: terraform fmt -check
continue-on-error: true
- name: Terraform Init
id: init
working-directory: ./infra
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_ENTRA_ID_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_ENTRA_ID_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_ENTRA_ID_TENANT_ID }}
ARM_USE_OIDC: true
run: terraform init
- name: Terraform Validate
id: validate
working-directory: ./infra
run: terraform validate -no-color
- name: Terraform Plan
id: plan
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_ENTRA_ID_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_ENTRA_ID_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_ENTRA_ID_TENANT_ID }}
ARM_USE_OIDC: true
working-directory: ./infra
run: terraform plan -no-color
- name: Terraform Apply
working-directory: ./infra
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_ENTRA_ID_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_AD_TENANT_ID }}
ARM_USE_OIDC: true
run: terraform apply -auto-approve