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

如何用Terraform管理KeyVault权限并清理非合规外部权限?

解决Azure KeyVault权限循环依赖+Terraform独占权限的方案

核心问题拆解

部署APIM、Function、AppService时,直接在azurerm_key_vault内嵌access_policy引用未创建的MSI会触发循环依赖(KV需要MSI ID,MSI所属资源需要KV存在);改用单独azurerm_key_vault_access_policy或RBAC虽能破循环,但Terraform无法清理外部添加的权限。要同时满足破循环和Terraform独占权限管理,可采用以下两种方案:


方案1:拆分KV部署顺序,保留内嵌access_policy(推荐)

通过调整资源创建顺序,先建基础KV,再建依赖资源,最后更新KV权限,既破循环,又保留Terraform对权限的独占管理(自动清理外部权限)。

实现步骤

  1. 先创建带初始权限的KeyVault:只添加部署账号或管理员的必要权限,不引用未创建的MSI。
  2. 创建依赖KV的资源:部署APIM、Function、AppService等,此时它们的系统/用户分配MSI会被创建,且可正常引用KV的密钥/证书。
  3. 在同一个KV资源中添加MSI的access_policy:通过depends_on强制Terraform先完成依赖资源的创建,再更新KV权限。

代码示例

# 1. 创建基础KeyVault(仅初始权限)
resource "azurerm_key_vault" "main" {
  name                       = "prod-kv-001"
  location                   = azurerm_resource_group.main.location
  resource_group_name        = azurerm_resource_group.main.name
  tenant_id                  = data.azurerm_client_config.current.tenant_id
  soft_delete_retention_days = 7
  purge_protection_enabled   = true

  # 仅添加部署账号的初始权限,避免依赖未创建的MSI
  access_policy {
    tenant_id = data.azurerm_client_config.current.tenant_id
    object_id = data.azurerm_client_config.current.object_id

    key_permissions     = ["Get", "List"]
    secret_permissions  = ["Get", "List"]
    certificate_permissions = ["Get", "List"]
  }

  # 强制先创建完所有依赖资源,再更新KV的access_policy
  depends_on = [
    azurerm_function_app.order_processor,
    azurerm_api_management.customer_portal,
    azurerm_app_service.web_ui
  ]
}

# 2. 创建带系统分配MSI的Function App
resource "azurerm_function_app" "order_processor" {
  name                       = "func-order-processor-001"
  location                   = azurerm_resource_group.main.location
  resource_group_name        = azurerm_resource_group.main.name
  app_service_plan_id        = azurerm_app_service_plan.func_plan.id
  storage_account_name       = azurerm_storage_account.func_storage.name
  storage_account_access_key = azurerm_storage_account.func_storage.primary_access_key

  identity {
    type = "SystemAssigned"
  }

  # 引用KeyVault中的秘密(此时KV已存在,无循环依赖)
  app_settings = {
    "DB_CONN_STRING" = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.db_conn.id})"
  }
}

# 3. 回到KV资源,添加Function App MSI的权限(同一块资源中追加)
resource "azurerm_key_vault" "main" {
  # 直接追加MSI的access_policy即可,Terraform会合并更新
  access_policy {
    tenant_id = azurerm_function_app.order_processor.identity[0].tenant_id
    object_id = azurerm_function_app.order_processor.identity[0].principal_id

    secret_permissions = ["Get"]
  }

  # 同理添加APIM、AppService的access_policy
  access_policy {
    tenant_id = azurerm_api_management.customer_portal.identity[0].tenant_id
    object_id = azurerm_api_management.customer_portal.identity[0].principal_id

    key_permissions = ["Get"]
  }
}

效果说明

  • 打破循环:KV先创建,依赖资源可正常引用KV,之后再更新权限。
  • 独占权限管理:Terraform会将配置中的所有access_policy视为完整集合,任何通过门户或外部工具添加的权限,都会在terraform apply时被自动移除。

方案2:单独access_policy资源+生命周期强制替换

如果需要拆分权限管理,可使用azurerm_key_vault_access_policy资源,通过生命周期规则强制Terraform覆盖所有权限,实现独占管理。

实现步骤

  1. 创建KeyVault时,设置lifecycle { ignore_changes = [access_policy] },避免内嵌权限干扰。
  2. 用for_each批量管理所有azurerm_key_vault_access_policy资源,确保所有权限都在配置中定义。
  3. 为每个access_policy资源设置replace_triggered_by,触发时强制替换所有权限,清理外部添加项。

代码示例

resource "azurerm_key_vault" "main" {
  name                       = "prod-kv-001"
  location                   = azurerm_resource_group.main.location
  resource_group_name        = azurerm_resource_group.main.name
  tenant_id                  = data.azurerm_client_config.current.tenant_id
  soft_delete_retention_days = 7
  purge_protection_enabled   = true

  # 忽略内嵌access_policy的变化,交由单独资源管理
  lifecycle {
    ignore_changes = [access_policy]
  }
}

# 用for_each管理所有权限,确保配置覆盖所有需要的MSI
locals {
  kv_permissions = {
    "func-order-processor" = {
      object_id = azurerm_function_app.order_processor.identity[0].principal_id
      tenant_id = azurerm_function_app.order_processor.identity[0].tenant_id
      secret_perms = ["Get"]
    },
    "apim-customer-portal" = {
      object_id = azurerm_api_management.customer_portal.identity[0].principal_id
      tenant_id = azurerm_api_management.customer_portal.identity[0].tenant_id
      key_perms = ["Get"]
    }
  }
}

resource "azurerm_key_vault_access_policy" "all" {
  for_each = local.kv_permissions

  key_vault_id = azurerm_key_vault.main.id
  tenant_id    = each.value.tenant_id
  object_id    = each.value.object_id

  secret_permissions = each.value.secret_perms != null ? each.value.secret_perms : []
  key_permissions    = each.value.key_perms != null ? each.value.key_perms : []

  # 触发替换时,强制重新创建所有access_policy,清理外部添加项
  lifecycle {
    replace_triggered_by = [azurerm_key_vault.main.id]
  }
}

效果说明

  • 破循环:单独的access_policy资源可通过depends_on依赖MSI所属资源,避免循环。
  • 独占权限:每次terraform apply时,若触发替换(比如KV ID变化或手动执行terraform apply -replace=azurerm_key_vault_access_policy.all),Terraform会重新创建所有权限,自动移除未在配置中定义的外部权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:37:14