使用专用终结点创建函数应用程序和存储帐户时出现 Terraform 403 错误

Terraform 403 error when creating function app and storage account with private endpoint

我在创建通过 vnet 内的专用端点连接到其存储帐户的函数应用程序时收到 403 禁止。存储帐户的防火墙默认操作为 'Deny',当然,如果我将其设置为 'Allow',它将起作用。但是,我希望它是 'Deny'。在此 microsoft link 之后,如果函数应用程序和存储帐户是在具有 vnet、子网和专用端点的同一区域中创建的,那么它应该可以工作,所以我一定是做错了什么。我还尝试更改存储帐户的区域,但它仍然导致 403。

错误:

Error: web.AppsClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: Code="BadRequest" Message="There was a conflict. The remote server returned an error: (403) Forbidden." Details=[{"Message":"There was a conflict. The remote server returned an error: (403) Forbidden."},{"Code":"BadRequest"},{"ErrorEntity":{"Code":"BadRequest","ExtendedCode":"01020","Message":"There was a conflict. The remote server returned an error: (403) Forbidden.","MessageTemplate":"There was a conflict. {0}","Parameters":["The remote server returned an error: (403) Forbidden."]}}]

这是我的地形代码

resource "azurerm_function_app" "func" {
  name                       = "${var.func_basics.name}-func"
  location                   = var.func_basics.location
  resource_group_name        = var.func_basics.resource_group_name
  app_service_plan_id        = azurerm_app_service_plan.svc_plan.id
  storage_account_name       = azurerm_storage_account.func_sa.name
  storage_account_access_key = azurerm_storage_account.func_sa.primary_access_key
  version                    = var.runtime_version
  https_only                 = true
  depends_on = [
    azurerm_storage_account.func_sa,
    azurerm_app_service_plan.svc_plan,
    azurerm_application_insights.func_ai,
    azurerm_virtual_network.func_vnet
  ]

  app_settings = merge(var.app_settings, local.additional_app_settings)

}

resource "azurerm_app_service_plan" "svc_plan" {
  name                = "${var.func_basics.name}-func-plan"
  location            = var.func_basics.location
  resource_group_name = var.func_basics.resource_group_name
  kind                = "elastic"

  sku {
    tier = "ElasticPremium"
    size = "EP1"
  }
}

resource "azurerm_application_insights" "func_ai" {
  name                = "${var.func_basics.name}-func-appi"
  location            = var.func_basics.location
  resource_group_name = var.func_basics.resource_group_name
  application_type    = var.ai_app_type
}

resource "azurerm_storage_account" "func_sa" {
  name                      = "st${lower(replace(var.func_basics.name, "/[-_]*/", ""))}"
  resource_group_name       = var.func_basics.resource_group_name
  location                  = var.func_basics.location
  account_tier              = var.sa_settings.tier
  account_replication_type  = var.sa_settings.replication_type
  account_kind              = "StorageV2"
  enable_https_traffic_only = true
  min_tls_version           = "TLS1_2"
  depends_on = [
    azurerm_virtual_network.func_vnet
  ]

  network_rules {
    default_action = "Deny"
    virtual_network_subnet_ids = [azurerm_subnet.func_endpoint_subnet.id]
    bypass = [
      "Metrics",
      "Logging",
      "AzureServices"
    ]
  }
}

resource "azurerm_virtual_network" "func_vnet" {
  name                = "${var.func_basics.name}-func-vnet"
  resource_group_name = var.func_basics.resource_group_name
  location            = var.func_basics.location
  address_space       = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "func_service_subnet" {
  name                                          = "${var.func_basics.name}-func-svc-snet"
  resource_group_name                           = var.func_basics.resource_group_name
  virtual_network_name                          = azurerm_virtual_network.func_vnet.name
  address_prefixes                              = ["10.0.1.0/24"]
  enforce_private_link_service_network_policies = true

  service_endpoints = ["Microsoft.Storage"]

  delegation {
    name = "${var.func_basics.name}-func-del"

    service_delegation {
      name    = "Microsoft.Web/serverFarms"
      actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
    }
  }
}

resource "azurerm_subnet" "func_endpoint_subnet" {
  name                                           = "${var.func_basics.name}-func-end-snet"
  resource_group_name                            = var.func_basics.resource_group_name
  virtual_network_name                           = azurerm_virtual_network.func_vnet.name
  address_prefixes                               = ["10.0.2.0/24"]
  enforce_private_link_endpoint_network_policies = true
}

resource "azurerm_private_endpoint" "func_req_sa_blob_endpoint" {
  name                = "${var.func_basics.name}-func-req-sa-blob-end"
  resource_group_name = var.func_basics.resource_group_name
  location            = var.func_basics.location
  subnet_id           = azurerm_subnet.func_endpoint_subnet.id

  private_service_connection {
    name                           = "${var.func_basics.name}-func-req-sa-blob-pscon"
    private_connection_resource_id = azurerm_storage_account.func_sa.id
    is_manual_connection           = false
    subresource_names              = ["blob"]
  }
}

resource "azurerm_private_endpoint" "func_req_sa_file_endpoint" {
  name                = "${var.func_basics.name}-func-req-sa-file-end"
  resource_group_name = var.func_basics.resource_group_name
  location            = var.func_basics.location
  subnet_id           = azurerm_subnet.func_endpoint_subnet.id

  private_service_connection {
    name                           = "${var.func_basics.name}-func-req-sa-file-pscon"
    private_connection_resource_id = azurerm_storage_account.func_sa.id
    is_manual_connection           = false
    subresource_names              = ["file"]
  }
}

resource "azurerm_app_service_virtual_network_swift_connection" "func_vnet_swift" {
  app_service_id = azurerm_function_app.func.id
  subnet_id      = azurerm_subnet.func_service_subnet.id
}

locals {
  additional_app_settings = {
    "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.func_ai.instrumentation_key
    "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.func_sa.primary_connection_string
    "AzureWebJobsStorage"                      = azurerm_storage_account.func_sa.primary_connection_string
    "WEBSITE_VNET_ROUTE_ALL"                   = "1"
    "WEBSITE_CONTENTOVERVNET"                  = "1"
    "WEBSITE_DNS_SERVER"                       = "168.63.129.16"
  }
}

这似乎是创建 Azure 函数时常见的错误消息,其中函数的存储帐户已添加到虚拟网络,请阅读 here 了解更多详细信息。

要解决它,您可以使用 local-exec Provisioner 在所有设置完成后调用 az CLI 命令拒绝流量。

az storage account update --name storage_account_name --resource-group reource_group_name --default-action 'Deny' --bypass 'AzureServices', 'Logging', 'Metrics'

或者,您可以单独配置存储帐户网络规则。您可能需要允许您客户端的 IP 访问存储帐户。

    resource "azurerm_storage_account_network_rules" "test" {
      resource_group_name  = var.resourceGroupName
      storage_account_name = azurerm_storage_account.func_sa.name
    
    
        default_action = "Deny"
       
        bypass = [
          "Metrics",
          "Logging",
          "AzureServices"
        ]
       ip_rules  = ["x.x.x.x"]
       
        depends_on = [
        azurerm_storage_account.func_sa,
        azurerm_app_service_plan.svc_plan,
        azurerm_application_insights.func_ai,
        azurerm_virtual_network.func_vnet,
        azurerm_function_app.func
      ]

}

此外,there 是 Github 上类似案例的可能解决方案。

我以前遇到过这个问题,发现可以按如下方式解决。我已经使用 azurerm_windows_function_app 资源在提供程序的 v3.3.0 上对此进行了测试。我认为目前这是一个 Azure 问题,因为如果您不提供共享,它会尝试创建一个,但会被拒绝。如果启用了允许受信任服务列表中的 Azure 服务访问此存储帐户,但 webapps 不受信任,你会希望它能工作。

  • 使用 IP 规则创建您的存储帐户并拒绝
  • 在此为您的函数应用内容创建共享
  • 在函数内设置以下配置设置

WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = <storage_account.primary_connection_string>

WEBSITE_CONTENTSHARE = <your share>

WEBSITE_CONTENTOVERVNET = 1

  • 在功能站点配置中设置属性 vnet_route_all_enabled = true