Azure Workload Identity Federation and GitHub Actions
Tags: azure, github, githubactions
Published: 2023-05-02 09:24:00

## The hard life of a GitHub Action
GitHub Actions ([documentation here](https://docs.github.com/en/actions)) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tksenz58ty9znlnn1ajk.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81h9a5kp4ou1p9qicfrj.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbphahp21fmrprwrdhz0.png)

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](https://learn.microsoft.com/en-us/azure/role-based-access-control/overview)) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bt2blpnuixjkin5h1ml8.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xas70cacpi9sos4vrsdi.png)

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!! :worried:
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:

```yaml
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 }}
```

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):

```json
{ 
    "clientId": "********-****-****-****-************",
    "clientSecret": "Lsu************************bAe", 
    "tenantId": "********-****-****-****-************" 
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7bo8a6qacf1qn4no2ds.png)

![The AZURE_CREDENTIALS secret page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3k7adusiftajg4mpxcns.png)

It is easy but...the `clientSecret `property 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](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc)).

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7kz06ndyavvagnt200c4.png)

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

![The trust configuration page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m2q3b9ak1maf31v498x.png)

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:

```yaml
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 }}

```

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!!