DEV Community

Cover image for Azure Workload Identity Federation and GitHub Actions
Massimo Bonanni
Massimo Bonanni

Posted on

Azure Workload Identity Federation and GitHub Actions

The hard life of a GitHub Action

GitHub Actions (documentation here) can be used to create entire environment in Azure and, in general, interact with the Azure platform.
But GitHub Actions run outside your Azure tenant (your "kingdom") and, for this reason, must be authenticated by your Azure Active Directory, and like all applications that run outside your tenant, they can use a Service Principal to be recognized.

In the beginning is the Service Principal

The first step you need to do to secure your GitHub Actions in Azure, is to create a Service Principal.
You have different ways to do that, but if you want to use the Azure portal, you can start from Active Directory page, choose the "App registration" blade and the "New registration" button:

The App registration page in Azure portal

Azure portal opens the "Register an application" page and the only think you have to do is give a name to your Service Principal and click on the "Register" button.

The Service Principal creation page

In a minute, you will have your Service Principal and the portal shows you its "Overview" page where you can find some useful information to configure your GitHub Actions.

The Service Principal overview page

Unfortunately, the service principal alone is not enough to achieve our goal. We need to create a secret that our Actions will need to know in order to authenticate, and a RBAC role assignment (more info here) for the Service Principal to have the right permissions to manage resources within our subscriptions.

Both these operations are easy to do: to generate a secret for the service principal, you need to click on the "Certificates & secrets" blade, open the "Client secrets" tab and click on the "New client secret" button.

The certificates & secrets page

To add the new secret, you just edit the description (I suggest using a description that is short but explains what use you are creating the secret for) and the duration.

The creation of a new secret

After the secret creation remember to take notes of its value, because it accessible only immediately after the creation.
A secret has the bad (or good if we look at it from a security point of view) habit of having a deadline. The maximum duration that you can select is equal to two years and, obviously, you have to make sure that whoever uses the Service Principal for be recognized (in our case the GitHub Action) always has the updated secret.
In short, the secret must be managed and this could involve a great deal of work if there are many Actions that use it or if we have many Service Principals.
This is the reason why, for example, you use the Managed Identities
when you want to secure the interaction between two services in Azure....but your GitHub Actions aren't in Azure!! 😟
The triad of values ApplicationID, TenantID and SecretValue are the Service Principal credentials you need in your GitHub Action.

Using secrets in GitHub Actions

Before showing you how to avoid using the secret in your GitHub Actions thanks to Federated Workload identities, let's see what are the steps to allow the GitHub Actions to interact with Azure thanks to the Service Principal's credentials.
Take a look of the following simple GitHub Action:

name: WIF App registration with Secret

on:
  workflow_dispatch

env: 
  LOCATION: "northeurope"
  RESOURCE_GROUP_NAME: "WIF-AppReg-Secret"
  SUBSCRIPTION_ID: "********-****-****-****-************" 

jobs:
    job01:
        runs-on: ubuntu-latest
        steps:
        - uses: Azure/login@v1
          with:
            creds: ${{ secrets.AZURE_CREDENTIALS }}
            allow-no-subscriptions: true
        - name: Create resource group
          uses: azure/CLI@v1
          with:
            inlineScript: |
                az group create --location ${{ env.LOCATION }} --name ${{ env.RESOURCE_GROUP_NAME }} --subscription ${{ env.SUBSCRIPTION_ID }}
Enter fullscreen mode Exit fullscreen mode

This Action uses two actions to open an authenticated session in Azure (Azure/login@v1) and run a simple Azure CLI command (azure/CLI@v1) to create a resource group.
To open an authenticated session in Azure, you need the credentials and credentials are represented by the previous triad ApplicationID, TenantID and SecretValue we mentioned earlier.
You must create the following JSON (substitute the right ids in the clientId, clientSecret and tenantId properties):

{ 
    "clientId": "********-****-****-****-************",
    "clientSecret": "Lsu************************bAe", 
    "tenantId": "********-****-****-****-************" 
}
Enter fullscreen mode Exit fullscreen mode

And create an Action Secret called AZURE_CREDENTIALS that contains the previous JSON as shows in the following picture:

The creation of an Action Secret

The AZURE_CREDENTIALS secret page

It is easy but...the clientSecretproperty must be updated (actually the entire secret in GitHub must be updated because secrets, in GitHub, cannot be modified but only overwritten) every time the secret in the app registration changes. And, finally, you must take care about clientSecret (and also clientId and tenantId) because if someone stoles those values, he can be impersonate your app registration and it isn't a good think!!

Workload Identity federation

This approach was born to trust tokens from external identity provider, such as GitHub or Google (or other in the future).
You first create a relationship between the identity (that can be a managed identity or an App registration) and the external identity provider.
Once this relationship is created, every time the workload wants to authenticate itself against AzureAD, it retrieves a token from the external IdP and, uses it to request access token from AAD.
It is not magic, behind the scene, AzureAD uses OpenID Connect (more info here).

GitHub Actions is one of the scenario you can leverage this approach.
To create the relationship between your App Registration and GitHub, you can open the "Certificates & secrets" blade for the App registration (as you did in the full credentials approach) and, instead create a secret, you use the "Federated credentials" tab.

The Federated credentials tab in the Certificates & secrets blade

The "Add credential" button opens the configuration page for the trust:

The trust configuration page

In the previous page, you must configure the repo you want to trust in terms of organization (or username) and repo name.
Then you need to choose the entity you want to trust and you can choose from Environment, Branch, Pull request or Tag. The value you choose must the configuration of your GitHub Actions. For example, if you action will start every time someone you push a new code in the main branch, you choose branch entity type and insert main in the branch name in the configuration.
The value you use for entity type will use by the OIDC flow to filer the scope of the OIDC requests.
Once you create the trust relationship, you can add clientId and tenantId values as action secrets in the repo and write the following action:

name: WIF App registration with federation

permissions:
  id-token: write # This is required for requesting the JWT
  contents: read  # This is required for actions/checkout

on:
  push:
    branches:
    - main
env: 
  LOCATION: "northeurope"
  RESOURCE_GROUP_NAME: "WIF-AppReg-Federation"

jobs:
    job01:
        runs-on: ubuntu-latest
        steps:
        - uses: Azure/login@v1
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - name: Creat resource group
          uses: azure/CLI@v1
          with:
            inlineScript: |
                az group create --location ${{ env.LOCATION }} --name ${{ env.RESOURCE_GROUP_NAME }}

Enter fullscreen mode Exit fullscreen mode

This action does exactly the same operations of the previous one, but here you haven't any clientSecret to manage.

NOTE: Keep attention to the permissions in the previous action. They are mandatory if you want that the action could retrieve token from the IdP. if you remove them, you receive an error!!

Top comments (0)