如何用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对权限的独占管理(自动清理外部权限)。
实现步骤
- 先创建带初始权限的KeyVault:只添加部署账号或管理员的必要权限,不引用未创建的MSI。
- 创建依赖KV的资源:部署APIM、Function、AppService等,此时它们的系统/用户分配MSI会被创建,且可正常引用KV的密钥/证书。
- 在同一个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覆盖所有权限,实现独占管理。
实现步骤
- 创建KeyVault时,设置
lifecycle { ignore_changes = [access_policy] },避免内嵌权限干扰。 - 用
for_each批量管理所有azurerm_key_vault_access_policy资源,确保所有权限都在配置中定义。 - 为每个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
相关产品推荐
相关产品推荐

