DEV Community

Cover image for GitHub Actions authentication methods for Azure
Marcel.L
Marcel.L

Posted on • Updated on

GitHub Actions authentication methods for Azure

Overview

When you work with GitHub Actions and start to write and develop automation workflows you will sometimes need to connect your workflows to different platforms, such as a cloud provider for example, to allow your workflows access and permissions to perform actions on the cloud provider. Thus you will need to connect and authenticate your GitHub Actions workflows with the cloud provider somehow.

Today we will look at two ways you can do this with Azure.

In both methods we will create what is known as an app registration/service principal, assigning permissions to the principal and link the principal with GitHub to allow your action workflows to authenticate and perform relevant tasks in Azure.

NOTE: If you are familiar with using Azure DevOps and Azure pipelines, this is synonymous to creating a service connection for your pipelines. GitHub Actions workflows are synonymous to Azure multi-stage YAML Pipelines.

Method 1 - Client and Secret (Legacy)

The first method we will look at is an older legacy method that uses a 'Client' and 'Secret' approach to authenticate.

1. Create Service Principal credentials

For this method I will use the following PowerShell script; 'Create-SP.ps1' to create an Azure AD App & Service Principal.

### Create-SP.ps1 ###
# Log into Azure
Az login

# Show current subscription (use 'Az account set' to change subscription)
Az account show

# variables
$subscriptionId=$(az account show --query id -o tsv)
$appName="GitHub-projectName-Actions"
$RBACRole="Contributor"

# Create AAD App and Service Principal and assign RBAC Role
az ad sp create-for-rbac --name $appName `
    --role $RBACRole `
    --scopes /subscriptions/$subscriptionId `
    --sdk-auth
Enter fullscreen mode Exit fullscreen mode

In the script above, the 'az ad sp create-for-rbac' command will create an AAD app & service principal and will output a JSON object containing the credentials of the service principal:

image.png

NOTE: The service principal will provide your GitHub Actions workflow, Contributor access to the Subscription. Feel free to change the RBAC 'role' and 'scopes' as necessary in the provided script.

Copy the JSON object as we will add this as a GitHub Secret. You will only need the sections with the "clientId", "clientSecret", "subscriptionId", and "tenantId" values:

{
  "clientId": "<GUID>",
  "clientSecret": "<PrincipalSecret>",
  "subscriptionId": "<GUID>",
  "tenantId": "<GUID>"
}
Enter fullscreen mode Exit fullscreen mode

The main drawback of using this method to create a service principal for GitHub is that the principals client secret is only valid for 1 year, and has to be managed and rotated frequently for security reasons, and will also have to be updated in your GitHub account manually, which can become a cumbersome administration task.

You can rotate the secret of the service principal by navigating to 'App registrations' in 'Azure Active Directory (AAD)' and finding the App we just created.

image.png

image.png

We will discuss later why using a passwordless approach with Open ID Connect (OIDC) is a much better option.

2. Create a GitHub Actions Secret

Next create a GitHub Secret on your GitHub repository using the copied JSON object Service Principal credentials from the previous step:

In the GitHub UI, navigate to your repository and select 'Settings' -> 'Secrets' -> 'Actions':

image.png

Select New repository secret to add the following secrets:

Secret Value
AZURE_CREDENTIALS The entire JSON output from the service principal creation step

image.png

3. Authenticate GitHub Actions workflows with Azure

Now that we have a GitHub Secret called 'AZURE_CREDENTIALS' that contains our Azure Service Principal credentials, we can consume this secret inside of our workflows to authenticate and log into Azure.

Here is an example workflow that will authenticate to Azure and show all resource groups on the subscription as part of the workflow run: authenticate-azure.yml.

name: Authenticate Azure
on:
  workflow_dispatch:
  pull_request:
    branches:
      - master

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v3.6.0

      - name: 'Log into Azure using github secret AZURE_CREDENTIALS'
        uses: Azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}
          enable-AzPSSession: true

      - name: 'Run az commands'
        run: |
          az account show
          az group list
Enter fullscreen mode Exit fullscreen mode

Notice the GitHub Actions step we are using to log into Azure:

- name: 'Log into Azure using github secret AZURE_CREDENTIALS'
uses: Azure/login@v1
with:
    creds: ${{ secrets.AZURE_CREDENTIALS }}
    enable-AzPSSession: true
Enter fullscreen mode Exit fullscreen mode

image.png

NOTE: The 'Run az commands' step will display the Azure account as well as a list of all resource groups.

Method 2 - Open ID Connect(OIDC) (Modern)

In this section we will look at a newer, more modern way to link GitHub Actions to Azure, whereby no client secrets are needed.

We will be using federated credentials with Open ID connect (OIDC). One of the main benefits of using OIDC is that there are no passwords or client secrets to manage or maintain and uses an 'identity' driven approach to authenticate.

1. Create Service Principal

As before we will require an AAD App and Service Principal.

You can use the following PowerShell script; 'Create-SP-OIDC.ps1' to create an Azure AD App & Service Principal with federated GitHub Action credentials.

### Create-SP-OIDC.ps1 ###
# Log into Azure
Az login

# Show current subscription (use 'Az account set' to change subscription)
Az account show

# variables
$subscriptionId = $(az account show --query id -o tsv)
$appName = "GitHub-projectName-Actions-OIDC"
$RBACRole = "Contributor"

$githubOrgName = "Pwd9000-ML"
$githubRepoName = "RandomStuff"
$githubBranch = "master"

# Create AAD App and Principal
$appId = $(az ad app create --display-name $appName --query appId -o tsv)
az ad sp create --id $appId

# Create federated GitHub credentials (Entity type 'Branch')
$githubBranchConfig = [PSCustomObject]@{
    name        = "GH-[$githubOrgName-$githubRepoName]-Branch-[$githubBranch]"
    issuer      = "https://token.actions.githubusercontent.com"
    subject     = "repo:" + "$githubOrgName/$githubRepoName" + ":ref:refs/heads/$githubBranch"
    description = "Federated credential linked to GitHub [$githubBranch] branch @: [$githubOrgName/$githubRepoName]"
    audiences   = @("api://AzureADTokenExchange")
}
$githubBranchConfigJson = $githubBranchConfig | ConvertTo-Json
$githubBranchConfigJson | az ad app federated-credential create --id $appId --parameters "@-"

# Create federated GitHub credentials (Entity type 'Pull Request')
$githubPRConfig = [PSCustomObject]@{
    name        = "GH-[$githubOrgName-$githubRepoName]-PR"
    issuer      = "https://token.actions.githubusercontent.com"
    subject     = "repo:" + "$githubOrgName/$githubRepoName" + ":pull_request"
    description = "Federated credential linked to GitHub Pull Requests @: [$githubOrgName/$githubRepoName]"
    audiences   = @("api://AzureADTokenExchange")
}
$githubPRConfigJson = $githubPRConfig | ConvertTo-Json
$githubPRConfigJson | az ad app federated-credential create --id $appId --parameters "@-"

### Additional federated GitHub credential entity types are 'Tag' and 'Environment' (see: https://docs.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azcli#github-actions-example) ###

# Assign RBAC permissions to Service Principal (Change as necessary)
$appId | foreach-object {

    # Permission 1 (Example)
    az role assignment create `
        --role $RBACRole `
        --assignee $_ `
        --subscription $subscriptionId

    # Permission 2 (Example)
    #az role assignment create `
    #    --role "Reader and Data Access" `
    #    --assignee "$_" `
    #    --scope "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageName"
}
Enter fullscreen mode Exit fullscreen mode

Notice that the script creates federated GitHub Action credentials on the AAD App, you can view them or add more by navigating to 'App registrations' in 'Azure Active Directory (AAD)' and finding the App we just created.

image.png

image.png

Notice that there are no client secrets on the AAD App.

Instead, the script created two federated GitHub Action credentials, one using entity type of 'Branch' linked to my repositories 'master; branch, and one entity type of 'pull request' linked to my repository for any actions triggered by a 'Pull Request (PR)'

You can add more by clicking on '+ Add credential'.

image.png

Select 'GitHub Actions deploying Azure resources' for the federated credential scenario.

image.png

There are four entity types for Github action federated credentials. They are 'Environment', 'Branch', 'Pull Request' and 'Tag'.

image.png

Depending on what entity type you select, will automatically construct the 'Subject identifier', this value is used to establish a connection between your GitHub Actions workflow and Azure Active Directory. The value is generated from the GitHub details entered.

When the GitHub Actions workflow requests the Microsoft identity platform to exchange a GitHub token for an access token, the values in the federated identity credential are checked against the provided GitHub token. Before Azure will grant an access token, the request must match the conditions defined in the 'Subject identifier'.

  • For Jobs tied to an environment use: repo:<Organization/Repository>:environment:<Name>.
  • For Jobs not tied to an environment, include the ref path of the branch/tag based on the ref path used for triggering the workflow: repo:<Organization/Repository>:ref:<ref path>. For example, repo:myOrg/myRepo:ref:refs/heads/myBranch or repo:myOrg/myRepo:ref:refs/tags/myTag.
  • For workflows triggered by a pull request event use: repo:<Organization/Repository>:pull-request.

You can see more examples on the official Microsoft documentation.

Grab the 'CLIENT_ID' of the AAD application as we will need it in the next step.

image.png

2. Create GitHub Actions Secrets

Next create the following GitHub Secrets on your GitHub repository.

In the GitHub UI, navigate to your repository and select 'Settings' -> 'Secrets' -> 'Actions':

image.png

Select New repository secret to add the following secrets:

Secret Value
AZURE_CLIENT_ID The AAD Application (client) ID created in the previous step
AZURE_TENANT_ID The Azure Directory (tenant) ID
AZURE_SUBSCRIPTION_ID The Azure subscription ID

NOTE: There is no password/secret required.

image.png

3. Authenticate GitHub Actions workflows with Azure (OIDC)

Now that you have federated credentials as well as GitHub Secrets configured, you can now configure your workflows to use OIDC tokens to authenticate and log into Azure.

To update your workflows for OIDC, you will need to make two changes to your YAML:

  • Add a permissions setting with id-token: write for the token.
  • Use the azure/login action to exchange the OIDC token (JWT) for a cloud access token.

Here is an example workflow that will authenticate to Azure and show all resource groups on the subscription as part of the workflow run: authenticate-azure-oidc.yml.

name: Run Azure Login with OIDC
on:
  workflow_dispatch:
  pull_request:
    branches:
      - master

permissions:
  id-token: write
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: 'Az CLI login using OIDC'
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: 'Run az commands'
        run: |
          az account show
          az group list
Enter fullscreen mode Exit fullscreen mode

image.png

NOTE: The 'Run az commands' step will display the Azure account as well as a list of all resource groups.

Notice the GitHub Actions step we are using to log into Azure:

- name: 'Az CLI login'
uses: azure/login@v1
with:
    client-id: ${{ secrets.AZURE_CLIENT_ID }}
    tenant-id: ${{ secrets.AZURE_TENANT_ID }}
    subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
Enter fullscreen mode Exit fullscreen mode

Also notice that the 'permissions' on workflow uses 'id-token: write'. You won't be able to request the OIDC JWT ID token if the permissions setting for id-token is set to 'read' or 'none'.

permissions:
  id-token: write
  contents: read
Enter fullscreen mode Exit fullscreen mode

If you only need to fetch an OIDC token for a single job, then this permission can also be set within a job. For example:

jobs:
  job1:
    runs-on: ubuntu-latest

    permissions:
      id-token: write

    steps:
      - uses: actions/stale@v5
Enter fullscreen mode Exit fullscreen mode

Conclusion

As you can see, it is pretty easy to set up authentication between your GitHub Actions and Azure. I would highly recommend adopting the newer, Open ID Connect (OIDC) method if you aren't using that already as it is a lot more convenient not having to manage and maintain passwords/secrets of your Azure service principals and GitHub secrets manually.

OIDC is a better security option as it is an identity driven, passwordless authentication method that doesn't require frequent rotation or expiration of Azure service principal passwords/secrets.

I hope you have enjoyed this post and have learned something new. You can also find the code samples used in this blog post on my published GitHub page. ❤️

Author

Like, share, follow me on: 🐙 GitHub | 🐧 Twitter | 👾 LinkedIn

Oldest comments (2)

Collapse
 
ipo profile image
Ipoffiong

thank you so much for your post. Please how can i use certificate based azure spn to authenticate with Azure from github actions? thank you

Collapse
 
jakubkeller profile image
Jakub Keller

The cert one is easier. Method 1 it is.