azureazure-devopsazure-pipelinesterraform-provider-azureazure-rm

Getting Error while running Azure DevOps Pipeline "Error: building account: could not acquire access token to parse claims: clientCredentialsToken


I am getting the below error while my Terraform Plan stage is running in Azure DevOps pipeline

│ Error: building account: could not acquire access token to parse claims: clientCredentialsToken: received HTTP status 400 with response: {"error":"invalid_request","error_description":"AADSTS900023: Specified tenant identifier '$(tf_var_tenant_id)' is neither a valid DNS name, nor a valid external domain. Trace ID: 36698f56-c6c2-4135-b36c-c425b7260a00 Correlation ID: 2adf5100-4998-44bd-94df-58f47a860103 Timestamp: 2025-01-09 01:00:49Z","error_codes":[900023],"timestamp":"2025-01-09 01:00:49Z","trace_id":"36698f56-c6c2-4135-b36c-c425b7260a00","correlation_id":"2adf5100-4998-44bd-94df-58f47a860103","error_uri":"https://login.microsoftonline.com/error?code=900023"}

It was working fine but suddenly this error started coming. I checked my Tenant ID and it is correct in the variable group

Any help will be appreciated

This is my Pipeline Code YAML File

# Variable 'build_type' was defined in the Variables tab
# Variable 'code_directory' was defined in the Variables tab

variables:
- group: MN-ALZTF-ADO-001
- group: MN-ALZTF-BACKEND-001

parameters:
- name: management_group
  displayName: Select Management Group
  type: string
  default: PLATFORM
  values:
  - LANDINGZONES
  - PLATFORM
- name: terraform_action
  displayName: Select Terraform action 
  type: string
  default: Validate
  values:
  - Validate
  - Plan
  - Apply
  - Destroy
- name: project_directory
  displayName: Select Project Directory
  type: string
  default: platform_landing_zone
  values:
  - platform_landing_zone

trigger: none # Disable CI triggers.
jobs:
- job: Terraform
  timeoutInMinutes: 20       # 720 minutes = 12 hours // see: https://learn.microsoft.com/en-us/azure/devops/pipelines/process/phases?view=azure-devops&tabs=yaml#timeouts
  cancelTimeoutInMinutes: 10  # how much time to give 'run always even if cancelled tasks' before stopping them
  variables:
  - group: MN-ALZTF-ADO-001
  - group: MN-ALZTF-BACKEND-001
#  - group: Infracost
  displayName: "Terraform - ${{ parameters.terraform_action }} on ${{ parameters.management_group }} for ${{ parameters.project_directory }}"
  # NOTE: Comment out the Azure Pipelines pool if using a self-hosted agent
  pool:
    # Hosted
    name: Azure Pipelines
    vmImage: ubuntu-latest
    # Self-Hosted
    #name: Self-hosted Terraform 
  steps:
  - checkout: self # NOTE: if checking out only "self", the contents of the repo are placed in the root folder instead of inside a folder the same name as the repo unless a "path" is given
    path: $(Build.Repository.Name)
  - task: Bash@3
    displayName: 'Debug: Bash'
    inputs:
      targetType: 'inline'
      script: |
        echo "TF_VAR_tenant_id = $(TF_VAR_tenant_id)"
        echo "TF_VAR_client_id = $(TF_VAR_client_id)"
        echo "ARM_TENANT_ID = $(ARM_TENANT_ID)"
        echo "ARM_CLIENT_ID = $(ARM_CLIENT_ID)"
        echo "ARM_SUBSCRIPTION_ID = $(ARM_SUBSCRIPTION_ID)"
        echo "TF_VAR_connectivity_subscription_id = $(TF_VAR_connectivity_subscription_id)"
        echo "TF_VAR_devtest_subscription_id = $(TF_VAR_devtest_subscription_id)"
        echo "TF_VAR_identity_subscription_id = $(TF_VAR_identity_subscription_id)"
        echo "TF_VAR_management_subscription_id = $(TF_VAR_management_subscription_id)"
        echo "TF_VAR_production_subscription_id = $(TF_VAR_production_subscription_id)"
        echo "terraform_action = "${{ parameters.terraform_action }}""
        echo "ADO Project Name = $(System.TeamProject)"
        echo "Agent.BuildDirectory: $(Agent.BuildDirectory)"
        echo "Agent.HomeDirectory:  $(Agent.HomeDirectory)"
        echo "Agent.TempDirectory:  $(Agent.TempDirectory)"
        echo "Agent.WorkFolder:     $(Agent.WorkFolder)"
        echo "Build.ArtifactStagingDirectory: $(Build.ArtifactStagingDirectory)"
        echo "Build.Repository.LocalPath: $(Build.Repository.LocalPath)"
        echo "Build.SourcesDirectory: $(Build.SourcesDirectory)"
        echo "Build.StagingDirectory: $(Build.StagingDirectory)"
        echo "Pipeline.Workspace: $(Pipeline.Workspace)"
        echo "System.DefaultWorkingDirectory: $(System.DefaultWorkingDirectory)"
        echo "workingDirectory: $(System.DefaultWorkingDirectory)/${{ parameters.project_directory }}"
        echo "project_directory = "${{ parameters.project_directory }}""
        echo "templates_branch = "$(Build.SourceBranchName)""
        echo "state_file_name = mg-${{ parameters.management_group }}.repo-$(Build.Repository.Name).branch-$(Build.SourceBranchName).template-${{ parameters.project_directory }}.tfstate"
        echo "ls $(System.DefaultWorkingDirectory)"
        ls "$(System.DefaultWorkingDirectory)"
        echo "ls $(System.DefaultWorkingDirectory)/${{ parameters.project_directory }}"
        ls "$(System.DefaultWorkingDirectory)/${{ parameters.project_directory }}"
  - task: TerraformInstaller@0
    displayName: Install Terraform latest
  - task: TerraformTaskV3@3
    displayName: 'Terraform : Init'
    env:
      TF_VAR_tenant_id: $(TF_VAR_tenant_id)
      TF_VAR_client_secret: $(TF_VAR_client_secret)
      TF_VAR_client_id: $(TF_VAR_client_id)
      ARM_CLIENT_ID: $(CLIENT_ID)
      ARM_CLIENT_SECRET: $(CLIENT_SECRET)
      ARM_TENANT_ID: $(TENANT_ID)
      ARM_SUBSCRIPTION_ID: $(SUBSCRIPTION_ID)
      ARM_ACCESS_KEY: $(BACKEND_STORAGE_ACCOUNT_ACCESS_KEY)
      TF_VAR_connectivity_subscription_id: $(TF_VAR_connectivity_subscription_id)
      TF_VAR_devtest_subscription_id: $(TF_VAR_devtest_subscription_id)
      TF_VAR_identity_subscription_id: $(TF_VAR_identity_subscription_id)
      TF_VAR_management_subscription_id: $(TF_VAR_management_subscription_id)
      TF_VAR_production_subscription_id: $(TF_VAR_production_subscription_id)
    inputs:
      provider: 'azurerm'
      command: 'init'
      commandOptions: "-reconfigure"
      #commandOptions: "-backend-config=subscription_id=$(ARM_SUBSCRIPTION_ID)"
      workingDirectory: $(System.DefaultWorkingDirectory)/${{ parameters.project_directory }}
      backendServiceArm: $(backendServiceArm)
      backendAzureRmResourceGroupName: $(backendAzureRmResourceGroupName)
      backendAzureRmStorageAccountName: $(backendAzureRmStorageAccountName)
      backendAzureRmContainerName: $(backendAzureRmContainerName)
      backendAzureRmKey: "mg-${{ parameters.management_group }}.repo-$(Build.Repository.Name).branch-$(Build.SourceBranchName).template-${{ parameters.project_directory }}.tfstate"
  - task: TerraformTaskV4@4
    displayName: 'Terraform : Validate'
    inputs:
      provider: 'azurerm'
      command: 'validate'
      workingDirectory: $(System.DefaultWorkingDirectory)/${{ parameters.project_directory }}
      backendServiceArm: $(backendServiceArm)
      backendAzureRmResourceGroupName: $(backendAzureRmResourceGroupName)
      backendAzureRmStorageAccountName: $(backendAzureRmStorageAccountName)
      backendAzureRmContainerName: $(backendAzureRmContainerName)
      backendAzureRmKey: "mg-${{ parameters.management_group }}.repo-$(Build.Repository.Name).branch-$(Build.SourceBranchName).template-${{ parameters.project_directory }}.tfstate"
  # - task: InfracostSetup@1
  #   displayName: 'Setup Infracost'
  #   condition: and(succeeded(), eq('${{ parameters.terraform_action }}', 'Plan'))
  #   inputs:
  #     apiKey: $(INFRACOST_API_KEY)
  # - task: Bash@3
  #   displayName: 'Run Infracost'
  #   condition: and(succeeded(), eq('${{ parameters.terraform_action }}', 'Plan'))
  #   inputs:
  #     targetType: 'inline'
  #     script: |
  #       echo '[INFRACOST]'
  #       infracost breakdown --path="$(System.DefaultWorkingDirectory)/${{ parameters.project_directory }}" --format=table
  #       echo ''
  - task: CmdLine@2
    displayName: 'Terraform: Plan'
    condition: and(succeeded(), eq('${{ parameters.terraform_action }}', 'Plan'))
    env:
      TF_VAR_tenant_id: $(TF_VAR_tenant_id)
      TF_VAR_client_secret: $(TF_VAR_client_secret)
      TF_VAR_client_id: $(TF_VAR_client_id)
      ARM_CLIENT_ID: $(CLIENT_ID)
      ARM_CLIENT_SECRET: $(CLIENT_SECRET)
      ARM_TENANT_ID: $(TENANT_ID)
      ARM_SUBSCRIPTION_ID: $(MANAGEMENT_SUBSCRIPTION_ID)
      ARM_ACCESS_KEY: $(BACKEND_STORAGE_ACCOUNT_ACCESS_KEY)
      TF_VAR_connectivity_subscription_id: $(TF_VAR_connectivity_subscription_id)
      TF_VAR_devtest_subscription_id: $(TF_VAR_devtest_subscription_id)
      TF_VAR_identity_subscription_id: $(TF_VAR_identity_subscription_id)
      TF_VAR_management_subscription_id: $(TF_VAR_management_subscription_id)
      TF_VAR_production_subscription_id: $(TF_VAR_production_subscription_id)
      TF_VAR_vm_admin_password: $(TF_VAR_vm_admin_password)
    inputs:
      script: |
        cd $(System.DefaultWorkingDirectory)/${{ parameters.project_directory }}
        terraform plan --var-file=centralus.tfvars -input=false
  - task: CmdLine@2
    displayName: 'Terraform: Apply'
    condition: and(succeeded(), eq('${{ parameters.terraform_action }}', 'Apply'))
    env:
      TF_VAR_tenant_id: $(TF_VAR_tenant_id)
      TF_VAR_client_secret: $(TF_VAR_client_secret)
      TF_VAR_client_id: $(TF_VAR_client_id)
      ARM_CLIENT_ID: $(CLIENT_ID)
      ARM_CLIENT_SECRET: $(CLIENT_SECRET)
      ARM_TENANT_ID: $(TENANT_ID)
      ARM_SUBSCRIPTION_ID: $(MANAGEMENT_SUBSCRIPTION_ID)
      ARM_ACCESS_KEY: $(BACKEND_STORAGE_ACCOUNT_ACCESS_KEY)
      TF_VAR_connectivity_subscription_id: $(TF_VAR_connectivity_subscription_id)
      TF_VAR_devtest_subscription_id: $(TF_VAR_devtest_subscription_id)
      TF_VAR_identity_subscription_id: $(TF_VAR_identity_subscription_id)
      TF_VAR_management_subscription_id: $(TF_VAR_management_subscription_id)
      TF_VAR_production_subscription_id: $(TF_VAR_production_subscription_id)
    inputs:
      script: |
        cd $(System.DefaultWorkingDirectory)/${{ parameters.project_directory }}
        terraform apply -auto-approve
  - task: CmdLine@2
    displayName: 'Terraform: Destroy'
    condition: and(succeeded(), eq('${{ parameters.terraform_action }}', 'Destroy'))
    env:
      TF_VAR_tenant_id: $(TF_VAR_tenant_id)
      TF_VAR_client_secret: $(TF_VAR_client_secret)
      TF_VAR_client_id: $(TF_VAR_client_id)
      ARM_CLIENT_ID: $(CLIENT_ID)
      ARM_CLIENT_SECRET: $(CLIENT_SECRET)
      ARM_TENANT_ID: $(TENANT_ID)
      ARM_SUBSCRIPTION_ID: $(MANAGEMENT_SUBSCRIPTION_ID)
      ARM_ACCESS_KEY: $(BACKEND_STORAGE_ACCOUNT_ACCESS_KEY)
      TF_VAR_connectivity_subscription_id: $(TF_VAR_connectivity_subscription_id)
      TF_VAR_devtest_subscription_id: $(TF_VAR_devtest_subscription_id)
      TF_VAR_identity_subscription_id: $(TF_VAR_identity_subscription_id)
      TF_VAR_management_subscription_id: $(TF_VAR_management_subscription_id)
      TF_VAR_production_subscription_id: $(TF_VAR_production_subscription_id)
    inputs:
      script: |
        cd $(System.DefaultWorkingDirectory)/${{ parameters.project_directory }}
        terraform destroy -auto-approve
...

Solution

  • Variables in Azure pipelines are automatically converted to environment variables in script steps. They do get transformed (all upper case and unsupported characters are converted to _). This is true for all variables, except for secrets.

    I suspect you can reduce the env: blocks considerably because of that. I also suspect that the uppercasing of variables might be why your variables aren't being translated as you expect:

      - task: TerraformTaskV3@3
        displayName: 'Terraform : Init'
        env:
          # It might make sense to uppercase the variable below to match Azure Pipeline's behavior
          TF_VAR_CLIENT_SECRET: $(TF_VAR_client_secret)
          ARM_CLIENT_SECRET: $(CLIENT_SECRET)
          ARM_ACCESS_KEY: $(BACKEND_STORAGE_ACCOUNT_ACCESS_KEY)
    
          # These can probably be removed
          # TF_VAR_connectivity_subscription_id: $(TF_VAR_connectivity_subscription_id)
          # TF_VAR_devtest_subscription_id: $(TF_VAR_devtest_subscription_id)
          # TF_VAR_identity_subscription_id: $(TF_VAR_identity_subscription_id)
          # TF_VAR_management_subscription_id: $(TF_VAR_management_subscription_id)
          # TF_VAR_production_subscription_id: $(TF_VAR_production_subscription_id)