United Kingdom: +44 (0)208 088 8978

Azure Deploy with Farmer, GitHub Actions and Federated Credential

Are you tired of having to track the lifecycle of the secrets you use in your deployment pipelines? There is another way.

We're hiring Software Developers

Click here to find out more

Are you tired of having to track the lifetime of the secrets you use in your deployment pipelines? There is another way.

The conventional way

Traditionally, authentication to Entra ID is facilitated in one of two ways: client secrets or certificates. But these come with expiration dates and as such need babysitting, and potentially some custom automation. Secrets can also leak.

The third way

Federated Credentials are the new game in town. They enable identity federation for software workloads. With workload identity federation it is possible to avoid storing Azure credentials outside Azure by configuring a federated identity credential. This establishes trust between your application and an external identity provider. The external provider can then exchange trusted tokens for access tokens granting access to protected resources in Azure.

Let's see how we might quickly set up a basic example using GitHub Actions.

Proof of concept

Part 1: Farmer Script

To create the simplest snippet of .NET code that deploys an ARM template to Azure, follow these steps:

  • At commandline, inside an empty folder, create a new F# project
dotnet new console -lang f#
  • Add the Farmer library
dotnet add package farmer
  • Open the project and replace anything in the Program.fs file with this snippet:
open Farmer
open Farmer.Builders

let sa = storageAccount {
  name "oidcdeployertest0451"
}

let deployment = arm {
  location Location.UKSouth
  add_resource sa
}

deployment
|> Deploy.execute "oidc-deployer" Deploy.NoParameters |> ignore

Notice the script does not concern itself with authentication. It only defines the resources (in our case a single storage account in the UKSouth region), generates the ARM template and deploys it.

  • Create a GitHub project and push this code to its master branch, as that's the one some later steps will reference.
Part 2: Entra ID

The first thing we need to do is register an Entra AD Application. In Azure portal, select Microsoft Entra ID, then navigate to the App registrations blade. To register a new application, click + New registration. Fill in the necessary details and register the application. Make a note of Client Id, Tenant Id and Subscription Id, you will need these later.

To configure Federated Credentials, Select the application you just created. Under Certificates & secrets, click on Federated credentials. Click on + Add credential and select GitHub Actions as the identity provider. Specify the repository and branch that will be allowed to request tokens, for example:

Organization: your-org
Repository: your-repository
Entity type: Branch
GitHub branch name: master

Part 3: Permissions on the scope

App permissions need to be given to the target resource at a chosen scope, typically the Subscription or Resource Group. In our case we will go with the Subscription scope.

In Azure Portal, navigate to the subscription the GitHub Action will be deploying to and then navigate to the Access Control (IAM) blade. Hit the + Add button and switch to the Privileged administrator roles tab. Add the RBAC role of Contributor to app we just created in Entra ID. It can be found it in the User, group or service principal selector.

Part 4: Add Secrets to GitHub Actions Workflow

In your GitHub repository, navigate to Settings | Secrets and variables | Actions.
Add 3 repository secrets, labelled as:

  • AZURE_CLIENT_ID
  • AZURE_TENANT_ID
  • AZURE_SUBSCRIPTION_ID

Add their respective values as per the the Ids you made note of when you created the App registration in Entra ID.
Don't get confused about storing these 3 Ids as secrets in a GitHub workflow. Even if these Ids were to leak, at most they represent an accurate address of our Azure state, not keys to it by any means. These Ids on their own will not enable anyone to read or write anything in the estate. For that they'd need tokens, and those will only be exchanged with a trusted party. That trusted relationship is what we configured manually in Part 2.

Part 5: Update GitHub Actions Workflow

On your GitHub project page, navigate to Actions and click New workflow, then Set up a workflow yourself. You will be presented with an empty file in the YAML editor. Paste in the following script:

name: Deploy to Azure

on:
  push:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Login to Azure
      uses: Azure/login@v2.1.0
      with:
        client-id: ${{ secrets.AZURE_CLIENT_ID }}
        tenant-id: ${{ secrets.AZURE_TENANT_ID }}
        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

    - name: Deploy to Azure
      run: dotnet run

The workflow will be triggered on any push to the master branch, which you can witness the moment you commit this YAML file to the branch.

How the flow works

This defines the trigger:

on:
  push:
    branches:
      - master

These permissions are required for the federated identity authentication:

permissions:
id-token: write
contents: read

After the code is checked out, and before it is executed, the login action uses the set of Azure Ids we stored in the project secrets to establish the authentication context:

- name: Login to Azure
uses: Azure/login@v2.1.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

Now you know how to authenticate your GitHub Action to Azure without the need to reference any expiring secret values in either the YAML script or the GitHub project secrets.