You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure DevOps YAML流水线Terraform服务主体认证报错解决

问题描述

正在通过Azure DevOps搭建Terraform代码的CI/CD流水线,计划将基础设施切换为CI/CD模式。尝试通过YAML流水线中的服务连接在Azure租户中验证Terraform,相关配置如下:

YAML流水线代码

name: Azure Infrastructure CI/CD

trigger:
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

variables:
  - name: public_key
    value: $(public_key)
  - name: terraformSecret
    value: $(client_secret)

steps:
  - checkout: self
    submodules: true

  - task: TerraformInstaller@0
    inputs:
      terraformVersion: 'latest'

  - script: az login --service-principal -u "$(client_id)" -p $(client_secret) --tenant "$(tenant_id)"
    displayName: 'Azure CLI Login'

  - script: az account set --subscription "$(subscription_id)"
    displayName: 'Azure Subscription Set'
  
  - script: |
      terraform init
      terraform plan -out=tfplan \
        -var="public_key=${public_key}" \
        -var="client_secret=${terraformSecret}"
    displayName: 'Terraform Init and Plan'
    workingDirectory: .

  - script: |
      terraform apply -auto-approve tfplan
    displayName: 'Terraform Apply'
    workingDirectory: .

Terraform Provider文件

provider "azurerm" {
    features {}

    client_id       = "xxxxx"
    client_secret   = var.client_secret
    tenant_id       = "xxxxx"
    subscription_id = "xxxxx"
}

Terraform变量文件片段

variable "client_secret" {
  type        = string
}

已在Azure DevOps流水线中正确设置所有环境变量,但执行时出现报错:

Planning failed. Terraform encountered an error while generating this plan.

╷
│ Error: building AzureRM Client: Authenticating using the Azure CLI is only supported as a User (not a Service Principal).
│
│ To authenticate to Azure using a Service Principal, you can use the separate 'Authenticate using a Service Principal'
│ auth method - instructions for which can be found here: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret
│
│ Alternatively you can authenticate using the Azure CLI by using a User Account.
│
│ with provider["registry.terraform.io/hashicorp/azurerm"],
│ on providers.tf line 10, in provider "azurerm":
│ 10: provider "azurerm" {
│
╵

##[error]Bash exited with code '1'.

需要在保护client_secret的前提下,实现Terraform通过服务主体在YAML流水线中认证。

解决方案

方法一:使用Azure DevOps官方Terraform任务 + Azure服务连接(推荐)

Azure DevOps提供了专门的Terraform任务,可直接通过已配置的Azure资源管理器服务连接完成认证,无需手动处理密钥,安全性更高。

步骤如下:

  1. 在Azure DevOps中创建一个Azure资源管理器服务连接,选择服务主体认证方式,关联目标订阅。
  2. 修改YAML流水线,替换原有脚本任务为官方Terraform任务:
name: Azure Infrastructure CI/CD

trigger:
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

variables:
  - name: public_key
    value: $(public_key)
  # 无需手动定义client_secret,服务连接会自动注入

steps:
  - checkout: self
    submodules: true

  - task: TerraformInstaller@0
    inputs:
      terraformVersion: 'latest'

  # 使用Azure服务连接进行Terraform初始化
  - task: TerraformTaskV4@4
    displayName: 'Terraform Init'
    inputs:
      provider: 'azurerm'
      command: 'init'
      workingDirectory: '.'
      backendServiceArm: '你的Azure服务连接名称'
      backendAzureRmResourceGroupName: '你的存储账户资源组名'
      backendAzureRmStorageAccountName: '你的存储账户名'
      backendAzureRmContainerName: 'tfstate容器名'
      backendAzureRmKey: 'terraform.tfstate'

  # Terraform Plan
  - task: TerraformTaskV4@4
    displayName: 'Terraform Plan'
    inputs:
      provider: 'azurerm'
      command: 'plan'
      workingDirectory: '.'
      commandOptions: '-out=tfplan -var="public_key=$(public_key)"'
      environmentServiceNameAzureRM: '你的Azure服务连接名称'

  # Terraform Apply
  - task: TerraformTaskV4@4
    displayName: 'Terraform Apply'
    inputs:
      provider: 'azurerm'
      command: 'apply'
      workingDirectory: '.'
      commandOptions: 'tfplan'
      environmentServiceNameAzureRM: '你的Azure服务连接名称'
  1. 修改Terraform Provider文件,移除硬编码的认证信息,让任务自动注入:
provider "azurerm" {
    features {}
    # 无需手动指定client_id/secret/tenant/subscription,服务连接会通过环境变量传递
}

方法二:通过环境变量传递服务主体认证信息

Terraform的AzureRM提供商会自动读取特定环境变量完成认证,无需在Provider中硬编码或传递变量,同时利用Azure DevOps的秘密变量保护client_secret。

步骤如下:

  1. 保留Azure DevOps中已配置的client_id、client_secret、tenant_id、subscription_id秘密变量。
  2. 修改YAML流水线,移除az login相关脚本,直接注入环境变量:
name: Azure Infrastructure CI/CD

trigger:
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

variables:
  - name: public_key
    value: $(public_key)
  # 注入Terraform所需的环境变量
  - name: ARM_CLIENT_ID
    value: $(client_id)
  - name: ARM_CLIENT_SECRET
    value: $(client_secret)
  - name: ARM_TENANT_ID
    value: $(tenant_id)
  - name: ARM_SUBSCRIPTION_ID
    value: $(subscription_id)

steps:
  - checkout: self
    submodules: true

  - task: TerraformInstaller@0
    inputs:
      terraformVersion: 'latest'
  
  - script: |
      terraform init
      terraform plan -out=tfplan \
        -var="public_key=${public_key}"
    displayName: 'Terraform Init and Plan'
    workingDirectory: .

  - script: |
      terraform apply -auto-approve tfplan
    displayName: 'Terraform Apply'
    workingDirectory: .
  1. 修改Terraform Provider文件,移除认证相关配置:
provider "azurerm" {
    features {}
    # 自动读取环境变量中的ARM_*系列变量完成认证
}

方法三:修复现有CLI登录方式(不推荐)

如果一定要保留CLI登录方式,需要调整Azure CLI的输出格式,让Terraform能识别服务主体认证:

修改YAML中的az login脚本,添加--allow-no-subscriptions参数,并设置环境变量:

- script: |
    az login --service-principal -u "$(client_id)" -p $(client_secret) --tenant "$(tenant_id)" --allow-no-subscriptions
    az account set --subscription "$(subscription_id)"
    # 设置环境变量告知Terraform使用CLI认证(针对服务主体场景)
    export ARM_USE_AZUREAD=true
  displayName: 'Azure CLI Login'

同时修改Terraform Provider文件:

provider "azurerm" {
    features {}
    use_azuread_auth = true
    # 无需指定client_id/secret,通过CLI上下文认证
}

注意:此方法需要确保Azure CLI版本与Terraform AzureRM提供商版本兼容,且安全性不如前两种方法,不推荐在生产环境使用。

内容的提问来源于stack exchange,提问作者Gabe Dillin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 00:12:22