skip to Main Content

I have a Azure DevOps YAML pipeline and want to deploy resources with Terraform to Azure. As first step I need to run terraform init. For that I want to use TerraformTaskV4 (https://github.com/microsoft/azure-pipelines-terraform/blob/main/Tasks/TerraformTask/TerraformTaskV4/README.md):

- task: TerraformTaskV4@4
  displayName: Initialize Terraform
  inputs:
    provider: 'azurerm'
    command: 'init'
    backendServiceArm: 'your-backend-service-connection'
    backendAzureRmResourceGroupName: 'your-rg-name'
    backendAzureRmStorageAccountName: 'your-stg-name'
    backendAzureRmContainerName: 'your-container-name'
    backendAzureRmKey: 'state.tfstate'

But there are two challenges:

  1. I want to use another Azure subscription than related to the service connection. The service principal of the service connection has access to multiple subscriptions
  2. I am using a service connection with a service principal leveraging Workflow Identity Federation

How can I run terraform init accordingly?

2

Answers


  1. Chosen as BEST ANSWER
    1. To my knowledge it is not possible to select the subscription with TerraformTaskV4@4. It always takes the subscription related to the service connection. Therefore I switched to using AzureCLI@2 (https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-cli-v2?view=azure-pipelines)
    2. In a first step you need to have access to the service principal (via addSpnToEnvironment) in the AzureCLI@2 task and add the environment variables (https://developer.hashicorp.com/terraform/language/backend/azurerm#backend-azure-ad-service-principal-or-user-assigned-managed-identity-via-oidc-workload-identity-federation) to the environment. This has to happen in a first and separate task. Then you can create a subsequent task (in the same job) for e.g. terraform init

    Example:

    ...
    
    - task: AzureCLI@2
      displayName: "Set Environment Variables"
      inputs:
        azureSubscription: "${{ variables.service_connection_name }}"
        addSpnToEnvironment: true
        scriptType: pscore
        scriptLocation: inlineScript
        workingDirectory: $(System.DefaultWorkingDirectory)/terraform
        inlineScript: |
          Write-Host "##vso[task.setvariable variable=ARM_USE_OIDC]true"
          Write-Host "##vso[task.setvariable variable=ARM_OIDC_TOKEN]$env:idToken"
          Write-Host "##vso[task.setvariable variable=ARM_CLIENT_ID]$env:servicePrincipalId"
          Write-Host "##vso[task.setvariable variable=ARM_SUBSCRIPTION_ID]$(subscriptionId)"
          Write-Host "##vso[task.setvariable variable=ARM_TENANT_ID]$env:tenantId"
        
    - task: TerraformInstaller@1
      displayName: Install Terraform
      inputs:
        terraformVersion: ${{ variables.terraform_version }}
    
    - task: AzureCLI@2
      displayName: "Terraform init"
      inputs:
        azureSubscription: "${{ variables.service_connection_name }}"
        addSpnToEnvironment: true
        scriptType: pscore
        scriptLocation: inlineScript
        workingDirectory: $(System.DefaultWorkingDirectory)/terraform
        inlineScript: |
          terraform init -backend-config="resource_group_name=$(terraformBackendResourceGroupName)" -backend-config="storage_account_name=$(terraformBackendStorageAccountName)" -backend-config="container_name=$(terraformBackendContainerName)" -backend-config="key=$(terraformBackendKey)"
    
    ...
    

  2. If all of your Azure subscriptions are in the same Microsoft Entra ID tenant, you also can consider using Azure management group:

    1. Create a management group in your tenant on Azure Portal. Assign the service principal with the Reader role at least on the new management group.

    2. Move all of your Azure subscriptions into the new management group.

    3. Go to Azure DevOps to create a new ARM connection (Azure Resource Manager service connection) using workload identity federation. Since you have an existing service principal, you can select "Workload Identity federation (manual)".

      enter image description here

    4. Give a customized name to the new ARM connection to complete Step 1. Click "Next" to start Step 2.

      enter image description here

    5. Open the existing service principal on Azure Portal. Go to "Certificates & secrets" > "Federated credentials" tab to add a new credential. Fill in the required information on the new credential.

      • Federated credential scenario: Other issuer
      • Issuer: Copy from Step 2 on the new ARM connection window.
      • Subject identifier: Copy from Step 2 on the new ARM connection window.
      • Name: A customized name of the new credential.

      enter image description here

    6. Back to the new ARM connection window to finish Step 2.

      • Scope Level: Select "Management Group".
      • Management Group ID: The ID of your management group created above.
      • Management Group Name: The name of your management group created above.
      • Service Principal Id: The Application (client) ID of the service principal.
      • Tenant ID: The Tenant ID of the your tenant.

      enter image description here


    After above configuration, you can use the new ARM connection on the TerraformTaskV4@4 task and specify the Azure subscription ID like as below:

    1. Directly specify the subscription ID using the command argument "-backend-config=subscription_id=xxxx" on the task.

      . . .
      
      variables:
        backendArmConnection: 'MyArmConnection'
        backendSubscriptionId: 'xxxx'
        backendResourceGroup: 'xxxx'
        backendStorageAccount: 'xxxx'
        backendBlobContainer: 'xxxx'
        backendArmKey: 'xxxx.tfstate'
      
      steps:
      - task: TerraformInstaller@1
        displayName: 'Install Terraform'
        inputs:
          terraformVersion: ${{ variables.terraform_version }}
      
      - task: TerraformTaskV4@4
        displayName: 'Terraform init'
        inputs:
          provider: 'azurerm'
          command: 'init'
          commandOptions: '-backend-config=subscription_id=$(backendSubscriptionId)'
          backendAzureRmUseEntraIdForAuthentication: true
          backendServiceArm: '$(backendArmConnection)'
          backendAzureRmResourceGroupName: '$(backendResourceGroup)'
          backendAzureRmStorageAccountName: '$(backendStorageAccount)'
          backendAzureRmContainerName: '$(backendBlobContainer)'
          backendAzureRmKey: '$(backendArmKey)'
      
      . . .
      
    2. Set the subscription ID as the environment variable. Since the pipeline variables will be automatically mapped as environment variables (except secret variables), you can directly define a pipeline variable with the name "ARM_SUBSCRIPTION_ID" and set the subscription ID as the value.

      . . .
      
      variables:
        backendArmConnection: 'MyArmConnection'
        ARM_SUBSCRIPTION_ID: 'xxxx'
        backendResourceGroup: 'xxxx'
        backendStorageAccount: 'xxxx'
        backendBlobContainer: 'xxxx'
        backendArmKey: 'xxxx.tfstate'
      
      steps:
      - task: TerraformInstaller@1
        displayName: 'Install Terraform'
        inputs:
          terraformVersion: ${{ variables.terraform_version }}
      
      - task: TerraformTaskV4@4
        displayName: 'Terraform init'
        inputs:
          provider: 'azurerm'
          command: 'init'
          backendAzureRmUseEntraIdForAuthentication: true
          backendServiceArm: '$(backendArmConnection)'
          backendAzureRmResourceGroupName: '$(backendResourceGroup)'
          backendAzureRmStorageAccountName: '$(backendStorageAccount)'
          backendAzureRmContainerName: '$(backendBlobContainer)'
          backendAzureRmKey: '$(backendArmKey)'
      
      . . .
      

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search