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.