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资源管理器服务连接完成认证,无需手动处理密钥,安全性更高。
步骤如下:
- 在Azure DevOps中创建一个Azure资源管理器服务连接,选择服务主体认证方式,关联目标订阅。
- 修改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服务连接名称'
- 修改Terraform Provider文件,移除硬编码的认证信息,让任务自动注入:
provider "azurerm" { features {} # 无需手动指定client_id/secret/tenant/subscription,服务连接会通过环境变量传递 }
方法二:通过环境变量传递服务主体认证信息
Terraform的AzureRM提供商会自动读取特定环境变量完成认证,无需在Provider中硬编码或传递变量,同时利用Azure DevOps的秘密变量保护client_secret。
步骤如下:
- 保留Azure DevOps中已配置的
client_id、client_secret、tenant_id、subscription_id秘密变量。 - 修改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: .
- 修改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

