Contents

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 :

/21/01-tf-github-umi.png

  • 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.