Home How to use OpenID Connect identity tokens to authenticate CircleCI jobs with Azure
Post
Cancel

How to use OpenID Connect identity tokens to authenticate CircleCI jobs with Azure

Why use OpenID Connect in CI/CD?

  • We do not have to store long-lived credentials as secrets in our CI/CD tools.
  • We do not have to rotate credentials since they are no longer static.
  • We have more granular control over how workflows can use credentials.
  • We follow best practices in terms of authentication and authorization.

Overview

Authenticate CircleCI jobs with Azure using OIDC identity tokens

When a CircleCI job starts, CircleCI signs an OpenID Connect token and makes it available to a job. After that, job can present this token to Azure, which verifies its authenticity, grants a job temporary credentials, and permits it to take defined actions.

Video walkthrough

Prerequisites

  • Access to Azure Subscription with Owner access level.
  • Access to Azure Active Directory Tenant with at least the Application Developer access level. For deployment script we will require Application Administrator
  • Azure CLI
  • Azure Bicep
  • A CircleCI project

Create resources using Azure CLI

Create Azure AD application

1
appId=$(az ad app create --display-name circleci-oidc --query appId -o tsv)

Create service principal

1
az ad sp create --id $appId

Create Azure AD federated identity credentials

We have to make a POST request to MS Graph to add federated credentials. Let’s check Azure Portal what fields we have to provide to create credentials.

Add federated identity credentials in Azure portal

  • In an Issuer field, we set: "https://oidc.circleci.com/org/ORGANIZATION_ID"
  • In a Subject identifier field, we set: "org/ORGANIZATION_ID/project/PROJECT_ID/user/USER_ID"
  • In a Name field, we set: circleci-federated-identity
  • In a Description field, we set: CircleCI service account federated identity
  • In an Audience field, we set: ORGANIZATION_ID

Now we have to get missing data from CircleCI and Azure.

Retrieve ORGANIZATION_ID from CircleCI

ORGANIZATION_ID is a UUID identifying the current job’s project’s organization. You can find CircleCI organization id by navigating to Organization Settings > Overview on the https://app.circleci.com/

Retrieve PROJECT_ID and USER_ID

PROJECT_ID and USER_ID are UUIDs that identify the CircleCI project and the user that run the job. We can find PROJECT_ID in Project Settings > Overview and USER_ID in User Settings > Account Integration

Retrieve an object id of an Azure AD application

1
objectId=$(az ad app show --id $appId --query id -o tsv)

Create a JSON file called body.json where we will store data for an API request

1
2
3
4
5
6
7
8
9
{
  "name": "circleci-federated-identity",
  "issuer": "https://oidc.circleci.com/org/ORGANIZATION_ID",
  "subject": "org/ORGANIZATION_ID/project/PROJECT_ID/user/USER_ID",
  "description": "CircleCI service account federated identity",
  "audiences": [
    "ORGANIZATION_ID"
  ]
}
Wildcard characters aren't supported in any federated identity credential property value. In AWS and GCP we can bypass that limitation. I did not find a good solution in Azure yet.

Make a MS Graph POST request

1
az rest --method POST --uri "https://graph.microsoft.com/beta/applications/$objectId/federatedIdentityCredentials" --body @body.json

If we receive a response without errors we can check Azure Portal if a federated identity was created. Certificates and secrets in Azure AD application

Grant permissions for the service principal

In the below example, we grant the Reader role to our subscription scope.

1
az role assignment create --assignee $appId --role Reader --scope /subscriptions/<subscription-id>

Create resources using Azure Bicep

As a prerequisite, we have to use Azure CLI to create an identity and assign a role to it.

Create resource group and managed identity

1
2
az group create --name circleci-oidc-rg --location eastus
az identity create --name script-id --resource-group circleci-oidc-rg

Assign Application Administrator Role

We can find an id of that role in the documentation.

1
2
3
4
5
principalId=$(az identity show --name script-id --resource-group circleci-oidc-rg --query principalId --output tsv)

az rest --method POST \
--uri 'https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments' \
--body '{"@odata.type": "#microsoft.graph.unifiedRoleAssignment", "principalId": <principalId>, "roleDefinitionId": "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3", "directoryScopeId": "/"}'

Create parameters.json file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "appName": {
      "value": "circleci-oidc"
    },
    "federatedName": {
      "value": "circleci-federated-identity"
    },
    "organizationId": {
      "value": <circleci ORGANIZATION_ID>
    },
    "projectId": {
      "value": <circleci PROJECT_ID>
    },
    "userId": {
      "value": <circleci USER_ID>
    }
  }
}

Create script.bicep

1
az deployment group create --name oidc-001 --resource-group circleci-oidc-rg -f script.bicep -p parameters.json

Create readerrole.bicep

1
az deployment sub create --name oidc-002 --location eastus -f readerrole.bicep
We can extend that approach. We can create a module and create federated credentials in a for loop, passing parameters as a list of objects.

Run CircleCI job to test federated identity.

Create config.yml in .circleci folder in your git repository.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
version: 2.1

orbs:
  azure-cli: circleci/azure-cli@1.2.2

jobs:
  circleci-oidc:
    parameters:
      azure-sp:
        type: env_var_name
        default: AZURE_SP
      azure-sp-tenant:
        type: env_var_name
        default: AZURE_SP_TENANT
    executor: azure-cli/azure-docker
    steps:
      - run:
         name: Login to Azure.
         command: |
            az login --service-principal \
            --username ${<< parameters.azure-sp >>} \
            --tenant ${<< parameters.azure-sp-tenant >>} \
            --federated-token ${CIRCLE_OIDC_TOKEN}
      - run:
         name: Check Azure account.
         command: az account show

workflows:
  main:
   jobs:
    - circleci-oidc:
       context:
        - just_oidc

Add environment variables in CircleCI

  • Add AZURE_SP (application id, echo $appId)
  • Add AZURE_SP_TENANT (az account show –query tenantId -o tsv)

Add environment variables in CircleCI portal

Add context to CircleCI

In CircleCI jobs that use at least one context, the OpenID Connect ID token is available in the environment variable $CIRCLE_OIDC_TOKEN.

Add context in CircleCI portal

Push changes to git and check CircleCI

It takes time for the federated identity credential to be propagated throughout a region after being initially configured. A token request made several minutes after configuring the federated identity credential may fail because the cache is populated in the directory with old data. Due to that our job can fail with error AADSTS70021: No matching federated identity record found for presented assertion. We should double-check what we added in the Subject identifier field. If everything is set correctly our pipeline should finish the job with success.

CircleCI pipeline jobs output

This post is licensed under CC BY 4.0 by the author.