11. February 2025 By Sascha Gottfried
Hardening deployment pipelines with workload identity federation
Traditionally, deployment pipelines typically use long-lived credentials to access cloud providers such as Microsoft Azure. These identities had to be created, protected and updated in the identity provider (IdP) such as Microsoft Entra ID. A copy of the credentials is stored as a secret in DevOps platforms such as Azure DevOps or GitHub Enterprise. Each time the pipeline is executed, the credentials are submitted to the cloud resources protected by an identity provider so that artefacts can be rolled out or cloud infrastructure resources can be provisioned. A typical example is provisioning infrastructure with Terraform.
What are the typical technical and organisational problems with the traditional approach?
When setting up the pipelines, the credentials from the identity provider (IdP) must be stored securely in the DevOps platform. In projects, the associated responsibilities are often separate. Setting up pipelines is the responsibility of the project, while managing identities is one of the core tasks of the IT operations of the respective company. IT service providers like adesso request the credentials, while IT operations provide the sensitive information via a secure communication channel or store it directly in the DevOps platform. These credentials tend to be long-lived – depending on the environment, a validity period of three to 24 months is common in practice.
Once they have expired, they have to be renewed and the process outlined above has to be gone through again. One problem is that the validity period may end unexpectedly or at an inconvenient time, such as at the turn of the year or during holiday periods.
A modern approach
With OpenID Connect (OIDC), a different approach can be used. Pipelines request a short-lived token that is only valid for the duration of the pipeline. The need to store duplicated credentials in the DevOps platform and to rotate them regularly is eliminated. This is a significant advantage from a technical and organisational point of view.
The cloud provider must support OIDC and a trust relationship must be established. The trust relationship between the OIDC provider and the cloud provider ensures that only trusted resources on the DevOps platform can request a token to access resources in the cloud provider.
Cloud providers that support OIDC are Microsoft Azure, Amazon Web Services and Google Cloud Platform. DevOps platforms that support OIDC are Azure DevOps, GitLab, GitHub and HashiCorp. Microsoft refers to the feature as ‘Workload Identity Federation’, GitLab calls it ‘ID Token authentication’ and HashiCorp calls it ‘Dynamic Credentials’.
The public preview in Azure DevOps started in September 2023. In the summer of 2024, the release notes announced that the integration of OIDC had been fully completed.
We support you
Our experts are familiar with all the above DevOps platforms and can help you harden deployment pipelines with OpenID Connect.
Token exchange
The following diagram shows the general process by which a workload outside the cloud platform exchanges an external token for an access token, thereby gaining access to protected cloud resources.

First, an OIDC trust relationship must be created in the cloud provider. In the diagram, this is Microsoft Azure. When the pipeline is started in the DevOps platform, an OIDC token is automatically generated. This token contains specific information, so-called claims, about the requesting resource. The cloud provider verifies the issuer of the token and the specific information/claims of the requesting identity. After a successful validation of the identity, the cloud provider delivers a short-lived access token to the DevOps platform. The pipeline uses this access token to access the resources in the cloud provider. The token loses its validity when the pipeline is finished.
Understanding the OIDC token
Let's take a closer look at the OIDC or Workload Identity Token. The following example shows a decoded GitHub Enterprise token.
The headers typ, alg and kid are so-called header elements from the Javascript Object Signing and Encryption Framework (JOSE) and describe cryptographic properties of the token.
{
"typ": "JWT",
"alg": "RS256",
"kid": "j-fFp9evPJAzV5I2_58HY5UvdCK6Q4LLB1rnPOUfQAk"
}
The token payload contains standard claims such as aud, sub and iss for establishing the trust relationship, as well as JOSE claims such as exp, iat, jti and nbf. An OIDC provider can define custom claims. GitHub supports the custom claims repository, environment and ref. These claims can be used, for example, to limit the trust relationship to Git repositories of a specific GitHub organisation.
{
"sub": "repo:octo-org/octo-repo:environment:prod",
"iss": "https://token.actions.githubusercontent.com",
"aud": "https://github.com/octo-org",
"jti": "1192426d-b525-4fde-9d42-f238be437bbd",
"nbf": 1632492967,
"exp": 1632493867,
"iat": 1632493567,
"environment": "prod",
"repository": "octo-org/octo-repo",
"ref": "refs/heads/main",
"ref_type": "branch",
"event_name": "workflow_dispatch",
"repository_owner": "octo-org",
"repository_visibility": "private",
"workflow": "example-workflow",
"workflow_ref": "octo-org/octo-repo/.github/workflows/oidc.yml@refs/heads/main"
}
In the example shown above, a repository octo-org/octo-repo and a job environment prod are referenced in the subject claim ‘repo:octo-org/octo-repo:environment:prod’.
OIDC trust relationship in the cloud provider
The purpose of configuring an OIDC trust relationship in the cloud provider is to ensure that a short-lived access token is only issued if the subject claim and any other claims in the OIDC token are identical to the configuration of the OIDC trust relationship in the cloud provider.
The OIDC trust relationship is specific to each cloud provider. Microsoft Entra defines the subject and issuer claims as essential to the trust relationship. Microsoft Entra verifies that at least these claims in the OIDC token are identical to the values for subject and issuer in the OIDC trust relationship. In Microsoft Entra, an OIDC trust relationship is mapped via Federated Identity Credentials.
Example with GitHub Actions and Microsoft Azure
An example implementation with GitHub Actions and Microsoft Entra ID requires the creation of federated credentials in Entra ID and the creation of GitHub Actions workflows.
Federated Credentials
A user-assigned managed identity is created in the Entra ID tenant. The ‘GitHub Actions deploying Azure resources’ scenario is selected for creating the trust, so that specific GitHub entities can be used for configuring the trust.
The chosen security strategy for allocating access tokens is to only evaluate requests via OIDC tokens as trustworthy if a GitHub Actions workflow from the octo-org/octo-repo repository references a Job Environment prod. A request from a different GitHub repository or for a different Job Environment would not be validated as trustworthy.
The content of the subject claim is determined by the GitHub organisation octo-org, the repository octo-repo that contains the GitHub Actions workflow, and the job environment prod.
Subject claim as composite information:
repo:octo-org/octo-repo:environment:prod
Summary of the position of trust:
- The claim for subject is
repo:octo-org/octo-repo:environment:prod.
- The claim for issuer has the value for the GitHub OIDC provider
https://token.actions.githubusercontent.com.
- The claim for audience is
api://AzureADTokenExchange.
GitHub Actions
The workflow requires an authorisation id-token: write in the permissions keyword, which allows GitHub's OIDC provider to create a JSON Web Token for each execution of the GitHub Action Workflow.
permissions:
id-token: write
Ideally, the Terraform provider azurerm should be configured using environment variables. The environment variables ARM_CLIENT_ID, ARM_SUBCRIPTION_ID
and ARM_TENANT_ID
are used to reference the ‘user-assigned managed identity’ in the Entra ID Tenant. This identity information is stored in GitHub Secrets. The ARM_USE_AZUREAD
and ARM_USE_OIDC
environment variables are used to enable Entra authentication and the use of OIDC or Workload Identity Federation. It is important to note that no long-lived password is now stored in GitHub or in a GitHub Secret, nor is it passed to the Terraform provider via the ARM_CLIENT_SECRET
environment variable.
Workflow in the repository octo-org/octo-repo
name: "Terraform Plan"
permissions:
id-token: write
contents: read
jobs:
plan:
name: Terraform Plan
environment: prod
env:
ARM_CLIENT_ID: "[null]"
ARM_SUBSCRIPTION_ID: "[null]"
ARM_TENANT_ID: "[null]"
ARM_USE_AZUREAD: true
ARM_USE_OIDC: true
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Install Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: |
terraform init
- name: Terraform Plan
run: |
terraform plan
Using the keyword environment and referencing the Environments prod job is essential for the subject claim in the issued OIDC token to be generated correctly.
When the workflow listed above is started in the octo-org/octo-repo
repository, an OIDC token with the following subject claim is generated and validated as trusted by the cloud provider.
repo:octo-org/octo-repo:environment:prod
Our experts can help you update GitHub Actions workflows to apply managed identities and workload identity federation to access resources in Microsoft Azure.
Reusable workflows
Consistent behaviour for Workload Identity Federation across multiple GitHub Actions workflows can be ensured by using so-called ‘reusable workflows’. To do this, workflows like the one in the code example above are stored in a dedicated GitHub repository like octo-org/octo-workflows. To enable a workflow to be called or referenced by other workflows, the value workflow_call must be set in the on keyword.
on:
workflow_call:
Calling workflows
Calling workflows in other repositories of a GitHub organisation can reference reusable workflows in private or public repositories using the keyword uses and the following syntax:
{owner}/{repo}/.github/workflows/{filename}@{ref}
Example Calling workflows
name: Terraform plan
jobs:
terraform-plan:
uses: octo-org/octo-workflows/.github/workflows/terraform-plan.yml@v0.4.0
secrets: inherit
The transfer of secrets from the context of the calling workflow to the reusable workflow is achieved with secrets: inherit. Calling workflows that reference reusable workflows become compact and clear.
An example from a customer project
In the specific project, an Azure Landing Zone was set up with the Cloud Adoption Framework - Enterprise Scale Terraform module. Using the ‘module composition’ approach, the Azure Landing Zone was segmented into three areas: ‘Connectivity’, ‘Core’ and ‘Management’. A GitHub repository and a Terraform workspace were created for each segment or module. GitHub Actions workflows for the Core Terraform workflow (write, plan, apply) were provided in all three repositories. Consistent behaviour of the workflows in the respective repositories was ensured by using versioned, reusable workflows in a dedicated, private repository. The configuration for authentication with the Terraform Azure provider azurerm was externalised in each repository via GitHub variables or secrets, thus enabling the separation of responsibilities. By using OpenID Connect or short-lived tokens, the customer no longer has to worry about the maintenance effort involved in manually managing and renewing credentials, and the risk of secrets being disclosed no longer exists.
With adesso to secure, automated cloud deployments!
At adesso, we rely on workload identity federation to securely and reliably operate deployment pipelines. Would you like to learn more about our services in this context or how we can support your company?