Azure Active Directory

GitHub actions federated identity with Azure AD

A long-awaited feature for many is the ability to authenticate with Azure in a GitHub workflow. For a long time, this has been an integrated feature in Azure DevOps, and for some time GitHub actions have had the ability to authenticate using Azure Application registrations and secrets. But now, we can use federated credentials which utilize OIDC- This will completely remove the cumbersome task of rotating app secrets.

Configure an Azure Application with federated identity

At my company, I have decided we should look into moving pipelines from Azure DevOps to GitHub. Federated identity is now Generally Available (GA) which means we are allowed to work with a production solution in mind. So how do you set it up? my suggestion is to follow the official guide. Below is a script that will configure an Azure AD application with GitHub authentication enabled. The script assumes you use a branching strategy. However, you can change this to be any of the other entity types, altering the federationSubcject variable.

# creates an appregistration in Azure AD and connects it with a github repo
# use as an example only
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]
$gitHubRepoName,
[Parameter(Mandatory)]
[string]
$teamName,
[Parameter(Mandatory)]
[string]
$branchName = "main"
)
$organization = "myorgltd"
$appRegistrationName = "gh-$teamName-action-sp"
$appRegistrationDescription = "Used by $teamName for enabeling GitHub actions with Azure AD OIDC authentication"
$audience = "api://AzureADTokenExchange"
$ghIssuer = "https://token.actions.githubusercontent.com/"
$federationName = "action-$gitHubRepoName"
$federationSubject = "repo:$($organization)/$($gitHubRepoName):ref:refs/heads/$($branchName)"
# check if app exists
$GhAdApp = $null
$GhAdApp = Get-AzADApplication DisplayName $appRegistrationName
if ($GhAdApp) {
Write-Output "App registration already exist. Adding new credential for specified repo $gitHubRepoName"
$federatedCred = New-AzADAppFederatedCredential ApplicationObjectId $GhAdApp.Id `
Audience $audience Issuer $ghIssuer Subject $federationSubject `
Name $federationName Description "Used to authenticate pipeline in $gitHubRepoName"
}
# if app does not eist
else {
Write-Output "adding new app registration for $teamName with credentials for $gitHubRepoName"
$GhAdApp = New-AzADApplication DisplayName $appRegistrationName Description $appRegistrationDescription
$federatedCred = New-AzADAppFederatedCredential ApplicationObjectId $GhAdApp.Id `
Audience $audience Issuer $ghIssuer Subject $federationSubject `
Name $federationName Description "Used to authenticate pipeline in $gitHubRepoName"
}
$credentialObject = [PSCustomObject]@{
ApplicationName = "$($GhAdApp.DisplayName)"
ApplicationObjectId = "$($GhAdApp.Id)"
}
Write-Output $credentialObject | ConvertTo-Json

Configure repository secrets in GitHub using PowerShell

As you probably understand, I need to automate this entire process. If our developers are moving from DevOps to Github workflows, it has to be as easy as it can be. No one will move unless they see some benefits, and onboarding is just as easy as the current DevOps setup. I am pretty familiar with Azure as an identity platform, especially with app registrations and enterprise apps. So the above script was done quickly. On the other hand, I am not that familiar with GitHub APIs. I know there’s a CLI, but for various reasons, I need to look into the API, more specifically the repository secrets API. What you can see from the first part of the documentation is that you need to encrypt the secret before you make the put request.

Creates or updates an organization secret with an encrypted value. Encrypt your secret using LibSodium. 

GitHub api docs

Now I was in trouble. Should I write everything in .NET, or is it possible to solve this using PowerShell? For sure I would be better off using PowerShell, at least in the PoC phase. First I tried to load the .NET sodium DLLs in my script, but it did not work, and the approach is not that delightful. Luckily with some search engine work, I found a module that wraps Sodium, created specifically for creating secrets in GitHub. Thanks, Tyler! See my example script below.

# add secrets to repo
import-module PSSodium
$ghToken = "ghp_"
$headers = @{Authorization = "token " + $ghToken}
$secret = "fb4ca569-c690-4391-96d9-928e7a8fd7ff"
$repoName = "myrepo"
$organization = "myorgltd"
Invoke-RestMethod Method get Uri "https://api.github.com/repos/$($organization)/$($repoName)/actions/secrets" Headers $headers
$publicKey = (Invoke-RestMethod Method get Uri "https://api.github.com/repos/$($organization)/$($repoName)/actions/secrets/public-key" Headers $headers)
$encryptedSecret = ConvertTo-SodiumEncryptedString Text $secret PublicKey $($publicKey.key)
$secretBody = @"
{
"encrypted_value": "$encryptedSecret",
"key_id": "$($publicKey.key_id)"
}
"@
Invoke-RestMethod Method Put Uri "https://api.github.com/repos/$($organization)/$($repoName)/actions/secrets/AZURE_TENANT_ID" Headers $headers body $secretBody

What about the pipeline?

Next, you need a working pipeline. Lucky for you the documentation is way better when I first looked at this (before GA), so the example works just fine as it is. Anyway, there are a couple of things to point out. Namely the permissions and the setting for login without a subscription ID.

name: 'Federated identity test'
on:
push:
branches:
main
permissions:
id-token: write
contents: read
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
name: checkout
uses: actions/checkout@main
name: azure login
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
allow-no-subscriptions: true
view raw workflow.yaml hosted with ❤ by GitHub

Summary

I hope this short post on how to configure Azure and GitHub with federated identity helps you, and that it provided some more information than the official documentation does. If anything please reach out. I now have my scripts in place, and can start the full automation and migration from Azure DevOps pipelines.

Engage by commenting