Skip to content

Azure Provider: Authenticating using a Service Principal with Open ID Connect

Terraform supports a number of different methods for authenticating to Azure:


We recommend using either a Service Principal or Managed Service Identity when running Terraform non-interactively (such as when running Terraform in a CI server) - and authenticating using the Azure CLI when running Terraform locally.

Setting up an Application and Service Principal in Azure

A Service Principal is a security principal within Azure Active Directory which can be granted access to resources within Azure Subscriptions. To authenticate with a Service Principal, you will need to create an Application object within Azure Active Directory, which you will use as a means of authentication, either using a Client Secret, a Client Certificate, or OpenID Connect (which is documented in this guide). This can be done using the Azure Portal.

This guide will cover how to create an Application and linked Service Principal, and then how to assign federated identity credentials to the Application so that it can be used for authentication via OpenID Connect. Once that's done finally we're going to grant the Service Principal permission to manage resources in the Subscription - to do this we're going to assign contributor rights to the Subscription - however, it's possible to assign other permissions depending on your configuration.

Creating the Application and Service Principal

We're going to create the Application in the Azure Portal - to do this navigate to the Azure Active Directory overview within the Azure Portal - then select the App Registration blade. Click the New registration button at the top to add a new Application within Azure Active Directory. On this page, set the following values then press Create:

  • Name - this is a friendly identifier and can be anything (e.g. "Terraform")
  • Supported Account Types - this should be set to "Accounts in this organizational directory only (single-tenant)"
  • Redirect URI - you should choose "Web" for the URI type. the actual value can be left blank

At this point the newly created Azure Active Directory application should be visible on-screen - if it's not, navigate to the the App Registration blade and select the Azure Active Directory application.

At the top of this page, you'll need to take note of the "Application (client) ID" and the "Directory (tenant) ID", which you can use for the values of clientId and tenantId respectively.

Configure Azure Active Directory Application to Trust a GitHub Repository

An application will need a federated credential specified for each GitHub Environment, Branch Name, Pull Request, or Tag based on your use case. For this example, we'll give permission to main branch workflow runs.

-> Tip: You can also configure the Application using the azuread_application and azuread_application_federated_identity_credential resources in the AzureAD Terraform Provider.

Via the Portal

On the Azure Active Directory application page, go to Certificates and secrets.

In the Federated credentials tab, select Add credential. The Add a credential blade opens. In the Federated credential scenario drop-down box select GitHub actions deploying Azure resources.

Specify the Organization and Repository for your GitHub Actions workflow. For Entity type, select Environment, Branch, Pull request, or Tag and specify the value. The values must exactly match the configuration in the GitHub workflow. For our example, let's select Branch and specify main.

Add a Name for the federated credential.

The Issuer, Audiences, and Subject identifier fields autopopulate based on the values you entered.

Click Add to configure the federated credential.

Via the Azure API

az rest --method POST \
        --uri https://graph.microsoft.com/beta/applications/${APP_OBJ_ID}/federatedIdentityCredentials \
        --headers Content-Type='application/json' \
        --body @body.json

Where the body is:

{
  "name":"${REPO_NAME}-pull-request",
  "issuer":"https://token.actions.githubusercontent.com",
  "subject":"repo:${REPO_OWNER}/${REPO_NAME}:refs:refs/heads/main",
  "description":"${REPO_OWNER} PR",
  "audiences":["api://AzureADTokenExchange"],
}

See the official documentation for more details.

Granting the Application access to manage resources in your Azure Subscription

Once the Application exists in Azure Active Directory - we can grant it permissions to modify resources in the Subscription. To do this, navigate to the Subscriptions blade within the Azure Portal, then select the Subscription you wish to use, then click Access Control (IAM), and finally Add > Add role assignment.

Firstly, specify a Role which grants the appropriate permissions needed for the Service Principal (for example, contributor will grant Read/Write on all resources in the Subscription). There's more information about the built in roles available here.

Secondly, search for and select the name of the Service Principal created in Azure Active Directory to assign it this role - then press Save.

Configure Azure Active Directory Application to Trust a Generic Issuer

On the Azure Active Directory application page, go to Certificates and secrets.

In the Federated credentials tab, select Add credential. The 'Add a credential' blade opens. Refer to the instructions from your OIDC provider for completing the form, before choosing a Name for the federated credential and clicking the Add button.

Configuring the Service Principal in Terraform

\~> Note: If using the AzureRM Backend you may also need to configure OIDC there too, see the documentation for the AzureRM Backend for more information.

As we've obtained the credentials for this Service Principal - it's possible to configure them in a few different ways.

When storing the credentials as Environment Variables, for example:

export ARM_CLIENT_ID="00000000-0000-0000-0000-000000000000"
export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
export ARM_TENANT_ID="00000000-0000-0000-0000-000000000000"

The provider will use the armOidcToken environment variable as an OIDC token. You can use this variable to specify the token provided by your OIDC provider.

When running Terraform in GitHub Actions, the provider will detect the actionsIdTokenRequestUrl and actionsIdTokenRequestToken environment variables set by the GitHub Actions runtime. You can also specify the armOidcRequestToken and armOidcRequestUrl environment variables.

For GitHub Actions workflows, you'll need to ensure the workflow has write permissions for the idToken.

permissions:
  id-token: write
  contents: read

For more information about OIDC in GitHub Actions, see official documentation.

The following Terraform and Provider blocks can be specified - where 370 is the version of the Azure Provider that you'd like to use:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as azurerm from "./.gen/providers/azurerm";
/*The following providers are missing schema information and might need manual adjustments to synthesize correctly: hashicorp/azurerm.
For a more precise conversion please use the --provider flag in convert.*/
new azurerm.provider.AzurermProvider(this, "azurerm", {
  features: [{}],
  use_oidc: true,
});

-> Note: Support for OpenID Connect was added in version 3.7.0 of the Terraform AzureRM provider.

\~> Note: If using the AzureRM Backend you may also need to configure OIDC there too, see the documentation for the AzureRM Backend for more information.

More information on the fields supported in the Provider block can be found here.

At this point running either terraformPlan or terraformApply should allow Terraform to run using the Service Principal to authenticate.


It's also possible to configure these variables either in-line or from using variables in Terraform (as the oidcToken, oidcTokenFilePath, or oidcRequestToken and oidcRequestUrl are in this example), like so:

\~> NOTE: We'd recommend not defining these variables in-line since they could easily be checked into Source Control.

import * as cdktf from "cdktf";
/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as azurerm from "./.gen/providers/azurerm";
/*The following providers are missing schema information and might need manual adjustments to synthesize correctly: hashicorp/azurerm.
For a more precise conversion please use the --provider flag in convert.*/
/*Terraform Variables are not always the best fit for getting inputs in the context of Terraform CDK.
You can read more about this at https://cdk.tf/variables*/
const oidcRequestToken = new cdktf.TerraformVariable(
  this,
  "oidc_request_token",
  {}
);
const oidcRequestUrl = new cdktf.TerraformVariable(
  this,
  "oidc_request_url",
  {}
);
const oidcToken = new cdktf.TerraformVariable(this, "oidc_token", {});
const oidcTokenFilePath = new cdktf.TerraformVariable(
  this,
  "oidc_token_file_path",
  {}
);
new azurerm.provider.AzurermProvider(this, "azurerm", {
  client_id: "00000000-0000-0000-0000-000000000000",
  features: [{}],
  oidc_request_token: oidcRequestToken.value,
  oidc_request_url: oidcRequestUrl.value,
  oidc_token: oidcToken.value,
  oidc_token_file_path: oidcTokenFilePath.value,
  subscription_id: "00000000-0000-0000-0000-000000000000",
  tenant_id: "00000000-0000-0000-0000-000000000000",
  use_oidc: true,
});

More information on the fields supported in the Provider block can be found here.

At this point running either terraformPlan or terraformApply should allow Terraform to run using the Service Principal to authenticate.