Bicep CI/CD Handbook
Bicep CI/CD HandbookToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

OpenID Connect

We recommend using OpenId Connect (OIDC) to configure GitHub Actions to authenticate with your Azure environment.

Since OIDC doesn’t require storing any persistent credentials, there is no risk of accidentally exposing a persistent secret that could be used elsewhere to imitate your GitHub Action. This also means that there is no need to manually rotate your secret if it expires.

Best Practices

  • Use a separate OIDC connection to authenticate with different environments. If for example your repository is able to deploy to a production environment in CD, and a development environment in CI, there should be no possibility of a developer accidentally making changes to production from a CD run.
  • Follow the Principle of least privilege by configuring Azure Role-based access control (RBAC) such that your GitHub Actions have minimal access to your environment.

Configuring Access

This script assumes that you have the GitHub CLI (gh) and Azure CLI (az) utilities installed locally and logged in before running.

Run the following commands in a bash shell to configure OIDC for your GitHub repository:

  1. Create an environment through the GitHub UI.

  2. Configure the following bash variables:

    # The name of the GitHub environment
    environment="replaceme"
    # Your Azure Tenant ID
    tenantId="replaceme"
    # Your Azure Subscription ID
    subId="replaceme"
    # The name of the GitHub repository owner (organization or username)
    repoOwner="replaceme"
    # The name of the GitHub repository
    repoName="replaceme"
    # The name of the resource group you will be deploying to
    rgName="replaceme"
    # The location of the resource group you will be deploying to
    rgLocation="replaceme"
    
  3. Create the resource group:

    az account set -n "$subId"
    az group create \
      --location "$rgLocation" \
      --name "$rgName"
    
  4. Create the AAD application for your GitHub Action to authenticate as:

    appCreate=$(az ad app create --display-name $rgName)
    appId=$(echo $appCreate | jq -r '.appId')
    appOid=$(echo $appCreate | jq -r '.id')
    
    spCreate=$(az ad sp create --id $appId)
    spId=$(echo $spCreate | jq -r '.id')
    az role assignment create --role owner --assignee-object-id $spId --assignee-principal-type ServicePrincipal --scope /subscriptions/$subId/resourceGroups/$rgName
    
  5. Configure OIDC:

    repoSubject="repo:$repoOwner/$repoName:environment:$environment"
    az ad app federated-credential create --id $appOid --parameters '{"name":"'$repoName'_'$environment'","issuer":"https://token.actions.githubusercontent.com","subject":"'$repoSubject'","description":"GitHub OIDC Connection","audiences":["api://AzureADTokenExchange"]}'
    
  6. Configure your GitHub Environment Variables:

    gh variable set --repo "$repoOwner/$repoName" --env "$environment" AZURE_CLIENT_ID --body $appId
    gh variable set --repo "$repoOwner/$repoName" --env "$environment" AZURE_SUBSCRIPTION_ID --body $subId
    gh variable set --repo "$repoOwner/$repoName" --env "$environment" AZURE_TENANT_ID --body $tenantId
    gh variable set --repo "$repoOwner/$repoName" --env "$environment" AZURE_RESOURCE_GROUP --body $rgName
    

    NOTE: We recommend using GitHub Environment Variables instead of Secrets, because there is no risk of unauthorized access if they are public, and it can make debugging simpler. Although there is no security risk, if you would like to avoid your Azure tenantId or subscriptionId being logged, then we recommend instead using Secrets.

The full script is available in the starter repo here.

Accessing Azure in your GitHub Workflows

For each job that needs access to Azure, you will need the following configuration in your Workflow yaml.

  1. Configure the environment you want to access (replace <your_environment> with the name of your GitHub environment):

    environment:
      name: <your_environment>
    
  2. Configure job permissions:

    permissions:
      contents: read
      id-token: write
    
  3. Add a step to log in to Azure, using the azure/login@v1 action:

    - name: AzCLI login
      uses: azure/login@v1
      with:
        client-id: ${{ vars.AZURE_CLIENT_ID }}
        tenant-id: ${{ vars.AZURE_TENANT_ID }}
        subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}