Terraform Deployment on Azure with Github Actions and Workload Identity Federation
Deploying an infrastructure using Terraform with a CI/CD pipeline is a common practice. However, it often involves storing a secret (token, certificate, string,…) as en environment variable or something similar to authenticate to the cloud provider. With Azure, it is possible to use a Managed Identity, but with Github, it involves using a Self Hosted Runner in an Azure Virtual Machine and attach the Managed Identity to the machine. The goal of this article is to show how to use Federation Workload Identity to authenticate to Azure from a Github-hosted agent (where we can’t attach a Managed Identity), without storing any secret in Github.
The steps are the following:
- Create a Managed Identity and configure a Federated Credential for Github
- Grant permissions to the Managed Identity to access Azure resources
- Configure Terraform to use OIDC authentication for the connection to the backend (remote tfstate) and for deploying resources
- Configure the Github Action workflow to use the Federated Credential
Create a Managed Identity and configure a Federated Credential for Github
Creating a Managed Identity is straightforward in the Azure portal.
The Federated Credential for Github should look like that :
- Organization: the name of your personnal Github Org or the one of your company
- Repository: the name of the repository where the Terraform code is stored
- Entity: The type of event that the credential will be used for. In our case, it will be a commit to the
main
branch. - Name: The name of the credential (no impact whatsoever)
Grant permissions to the Managed Identity to access Azure resources
You have to grant sufficent permissions to the Manageed Identity to be able to deploy the resources you have defined in your Terraform code. In my case, I have granted the Contributor
role at the subscription level.
You should also grant the Storage Blob Data Contributor
role to the Managed Identity on the container of the storage account that will be used to store the Terraform state.
Configure Terraform to use OIDC authentication for the connection to the backend (remote tfstate) and for deploying resources
There’s two things to configure in your Terraform code:
- The authentication to the backend (remote tfstate)
- The authentication to the Azure subscription where your resources will be deployed
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
}
}
backend "azurerm" {
resource_group_name = "<storage_account_resource_group_name>"
storage_account_name = "<storage_account_name>"
container_name = "<container_name>"
key = "tf-example" # can be anything
use_oidc = true # To use OIDC to authenticate to the backend
client_id = "f0614a10-1111-4030-bf97-bad3770ff86d" # The client ID of the Managed Identity
subscription_id = "81d07db3-86e1-49db-90a8-994e8228c86d" # The subscription ID where the storage account exists
tenant_id = "aa30bc87-f8c4-43c9-87b3-6e5a1f9f0d8b" # The tenant ID where the subscription and the Managed Identity are
}
}
provider "azurerm" {
features {}
use_oidc = true # Use OIDC to authenticate to Azure
subscription_id = "81d07db3-86e1-49db-90a8-994e8228c86d"
}
resource "azurerm_resource_group" "rg_training" {
name = "rg-example"
location = "West Europe"
}
The two important blocks are the backend "azurerm"
and the provider "azurerm"
. The use_oidc
attribute is set to true
in both blocks, and the backend
also contains the reference of the Managed Identity
referencing the Federated Credential to use. To deploy resources to Azure, Terraform will rely on an Azure authentication performed in the Github Action workflow.
Configure the Github Action workflow to use the Federated Credential
First, we define 3 repository secrets :
- AZURE_CLIENT_ID: The client ID of the Managed Identity
- AZURE_SUBSCRIPTION_ID: The subscription ID where the resources will be deployed
- AZURE_TENANT_ID: The tenant ID where the Azure subscription and the Managed Identity are
The Github Action workflow should look like that:
name: 'Terraform'
on:
push:
branches: [ "main" ]
permissions:
contents: read
id-token: write
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: login
uses: Azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: TF Init
run: terraform init
- name: TF Apply
run: terraform apply --auto-approve
The first step is to define when the workflow should be triggered. In our case, it will be triggered on a push to the main
branch. It’s also what we configured in the Federated Credential.
The second stem is to give the workflow the write
permissions on the id-token
of the repository. It’s required to authenticate using the Federated Credential.
Then, finally, the workflow uses the Azure/login@v1
action to authenticate to Azure using the Federated Credential (and using the repository secrets that we defined earlier).
Both the terraform init
and terraform apply
will use OIDC to authenticate to Azure, without having to store a single secret in Github.