首次用Terraform通过专用端点部署Key Vault密钥失败求助
问题:首次Terraform部署Azure Key Vault及密钥时触发403禁止错误
问题现象
- 首次通过Terraform同时部署禁用公网访问的Azure Key Vault和密钥时,出现403错误;Key Vault已存在或第二次执行部署时可正常创建密钥
- 报错信息:
Error: checking for presence of existing Key "example-key1" (Key Vault "https://xx-xxx-xxx.vault.azure.net/"): keyvault.BaseClient#GetKey: Failure responding to request: StatusCode=403 -- Original Error: autorest/azure: Service returned an error. Status=403 Code="Forbidden" Message="Public network access is disabled and request is not from a trusted service nor via an approved private link.\r\nCaller: appid=xxx;oid=xxx;iss=xxx: xx-xxx-xxx;location=westeurope" InnerError={"code":"ForbiddenByConnection"}
with azurerm_key_vault_key.example, on main.tf line 97, in resource "azurerm_key_vault_key" "example": 97: resource "azurerm_key_vault_key" "example" {
环境配置
- 采用中心辐射(hub-spoke)网络模型,Key Vault强制禁用公网访问
- 使用现有
privatelink.vaultcore.azure.net专用DNS区域,已与中心VNet关联 - 分支VNet已连接到中心VNet,部署环境(VS Code/自托管代理)位于分支VNet内
已尝试的无效方案
- 在代码中添加1-20分钟的
time_sleep等待 - 将专用DNS区域直接与分支VNet关联
解决方案
核心问题是:Terraform等待私有端点创建完成后,Azure私有DNS解析记录尚未同步到分支VNet,导致请求仍走公网被拒绝。以下是可行修复方案:
方案1:强制使用私有IP访问Key Vault(推荐)
通过私有端点的私有IP直接访问Key Vault,绕过DNS解析延迟问题。修改azurerm_key_vault_key资源,添加endpoint参数:
resource "azurerm_key_vault_key" "example" { name = "key-test" key_vault_id = azurerm_key_vault.example.id key_type = "RSA" key_size = 2048 key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"] # 强制使用私有端点IP访问,跳过DNS解析 endpoint = "https://${azurerm_private_endpoint.example.private_service_connection[0].private_ip_address}" depends_on = [azurerm_private_endpoint.example] tags = { "source" = "Terraform" } lifecycle { ignore_changes = [tags] } }
方案2:等待DNS解析生效后创建密钥
通过本地执行脚本,验证Key Vault域名已解析到私有IP,再创建密钥:
resource "null_resource" "wait_for_dns" { provisioner "local-exec" { command = <<EOT until nslookup ${azurerm_key_vault.example.name}.vault.azure.net | grep -q "${azurerm_private_endpoint.example.private_service_connection[0].private_ip_address}"; do echo "Waiting for DNS resolution to propagate..." sleep 30 done EOT } depends_on = [azurerm_private_endpoint.example] } resource "azurerm_key_vault_key" "example" { # 保留原有配置 name = "key-test" key_vault_id = azurerm_key_vault.example.id key_type = "RSA" key_size = 2048 key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"] # 依赖DNS验证完成 depends_on = [null_resource.wait_for_dns] tags = { "source" = "Terraform" } lifecycle { ignore_changes = [tags] } }
方案3:临时启用公网访问(不推荐,仅应急)
创建Key Vault时临时启用公网访问,密钥创建完成后再禁用:
resource "azurerm_key_vault" "example" { name = "test-kv" location = data.azurerm_resource_group.resource_group.location resource_group_name = data.azurerm_resource_group.resource_group.name sku_name = "standard" tenant_id = data.azurerm_client_config.current.tenant_id soft_delete_retention_days = 7 purge_protection_enabled = true enabled_for_disk_encryption = false enabled_for_template_deployment = false enabled_for_deployment = false public_network_access_enabled = true # 初始临时启用 enable_rbac_authorization = true network_acls { default_action = "Deny" bypass = "AzureServices" } tags = { "source" = "Terraform" } lifecycle { ignore_changes = [tags] } } # 创建密钥后更新Key Vault,禁用公网访问 resource "azurerm_key_vault" "example_post_key" { name = azurerm_key_vault.example.name location = azurerm_key_vault.example.location resource_group_name = azurerm_key_vault.example.resource_group_name sku_name = azurerm_key_vault.example.sku_name tenant_id = azurerm_key_vault.example.tenant_id soft_delete_retention_days = azurerm_key_vault.example.soft_delete_retention_days purge_protection_enabled = azurerm_key_vault.example.purge_protection_enabled enabled_for_disk_encryption = azurerm_key_vault.example.enabled_for_disk_encryption enabled_for_template_deployment = azurerm_key_vault.example.enabled_for_template_deployment enabled_for_deployment = azurerm_key_vault.example.enabled_for_deployment public_network_access_enabled = false # 最终禁用 enable_rbac_authorization = azurerm_key_vault.example.enable_rbac_authorization network_acls { default_action = azurerm_key_vault.example.network_acls[0].default_action bypass = azurerm_key_vault.example.network_acls[0].bypass } tags = azurerm_key_vault.example.tags depends_on = [azurerm_key_vault_key.example] } resource "azurerm_key_vault_key" "example" { # 保留原有密钥配置 name = "key-test" key_vault_id = azurerm_key_vault.example.id key_type = "RSA" key_size = 2048 key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"] depends_on = [azurerm_key_vault.example] tags = { "source" = "Terraform" } lifecycle { ignore_changes = [tags] } }
核心原因
首次部署时,私有端点资源已创建,但Azure私有DNS区域的解析记录需要时间同步到分支VNet的DNS服务器。Terraform的time_sleep仅等待资源创建完成,无法感知DNS解析状态,导致请求Key Vault时仍解析为公网IP,触发403禁止错误。
内容的提问来源于stack exchange,提问作者user3617809
相关产品推荐
相关产品推荐

