Trying to get azurerm_linux_function_app
to work with azurerm_storage_account_network_rules
and azurerm_private_endpoint
.
Terraform v1.3.7
on darwin_arm64
+ provider registry.terraform.io/hashicorp/azurerm v3.38.0
This is what I'm trying.
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "func_rg" {
name = "func"
location = "eastus"
}
# Create the network VNET
resource "azurerm_virtual_network" "func" {
name = "func_vnet"
address_space = ["10.0.0.0/16"]
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
}
# Create a subnet for endpoint
resource "azurerm_subnet" "endpoint_subnet" {
name = "endpoint_subnet"
address_prefixes = ["10.0.0.0/24"]
virtual_network_name = azurerm_virtual_network.func.name
resource_group_name = azurerm_resource_group.func_rg.name
private_endpoint_network_policies_enabled = true
}
# Create subnet for functions
resource "azurerm_subnet" "func_subnet" {
name = "func_subnet"
resource_group_name = azurerm_resource_group.func_rg.name
virtual_network_name = azurerm_virtual_network.func.name
address_prefixes = ["10.0.1.0/24"]
service_endpoints = ["Microsoft.Storage"]
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.Web/serverFarms"
}
}
}
# Create nsg for subnets
resource "azurerm_network_security_group" "func" {
name = "func_nsg"
location = azurerm_resource_group.func_rg.location
resource_group_name = azurerm_resource_group.func_rg.name
}
# Associate nsg with func subnet
resource "azurerm_subnet_network_security_group_association" "func" {
subnet_id = azurerm_subnet.func_subnet.id
network_security_group_id = azurerm_network_security_group.func.id
}
# Associate nsg with endpoint subnet
resource "azurerm_subnet_network_security_group_association" "endpoint" {
subnet_id = azurerm_subnet.endpoint_subnet.id
network_security_group_id = azurerm_network_security_group.func.id
}
# Create blob Private DNS Zone
resource "azurerm_private_dns_zone" "blob_dns_zone" {
name = "privatelink.blob.core.windows.net"
resource_group_name = azurerm_resource_group.func_rg.name
}
# Create file Private DNS Zone
resource "azurerm_private_dns_zone" "file_dns_zone" {
name = "privatelink.file.core.windows.net"
resource_group_name = azurerm_resource_group.func_rg.name
}
# Create table Private DNS Zone
resource "azurerm_private_dns_zone" "table_dns_zone" {
name = "privatelink.table.core.windows.net"
resource_group_name = azurerm_resource_group.func_rg.name
}
# Create queue Private DNS Zone
resource "azurerm_private_dns_zone" "queue_dns_zone" {
name = "privatelink.queue.core.windows.net"
resource_group_name = azurerm_resource_group.func_rg.name
}
# Create blob Private DNS Zone Network Link
resource "azurerm_private_dns_zone_virtual_network_link" "blob_vnl" {
name = "blob_vnl"
resource_group_name = azurerm_resource_group.func_rg.name
private_dns_zone_name = azurerm_private_dns_zone.blob_dns_zone.name
virtual_network_id = azurerm_virtual_network.func.id
}
# Create file Private DNS Zone Network Link
resource "azurerm_private_dns_zone_virtual_network_link" "file_vnl" {
name = "file_vnl"
resource_group_name = azurerm_resource_group.func_rg.name
private_dns_zone_name = azurerm_private_dns_zone.file_dns_zone.name
virtual_network_id = azurerm_virtual_network.func.id
}
# Create table Private DNS Zone Network Link
resource "azurerm_private_dns_zone_virtual_network_link" "table_vnl" {
name = "table_vnl"
resource_group_name = azurerm_resource_group.func_rg.name
private_dns_zone_name = azurerm_private_dns_zone.table_dns_zone.name
virtual_network_id = azurerm_virtual_network.func.id
}
# Create queue Private DNS Zone Network Link
resource "azurerm_private_dns_zone_virtual_network_link" "queue_vnl" {
name = "queue_vnl"
resource_group_name = azurerm_resource_group.func_rg.name
private_dns_zone_name = azurerm_private_dns_zone.queue_dns_zone.name
virtual_network_id = azurerm_virtual_network.func.id
}
resource "azurerm_storage_account" "func" {
name = "rdtestfuncsa"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_storage_account_network_rules" "func" {
storage_account_id = azurerm_storage_account.func.id
default_action = "Deny"
ip_rules = ["***.***.***.***"]
virtual_network_subnet_ids = [azurerm_subnet.func_subnet.id]
bypass = ["Metrics", "Logging", "AzureServices"]
}
resource "azurerm_storage_container" "func" {
name = "func-sc"
storage_account_name = azurerm_storage_account.func.name
container_access_type = "private"
}
# Create Private Endpints
resource "azurerm_private_endpoint" "file_endpoint" {
name = "rdtest_file_pe"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
subnet_id = azurerm_subnet.endpoint_subnet.id
private_service_connection {
name = "rdtest_file_psc"
private_connection_resource_id = azurerm_storage_account.func.id
is_manual_connection = false
subresource_names = ["file"]
}
}
resource "azurerm_private_endpoint" "blob_endpoint" {
name = "rdtest_blob_pe"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
subnet_id = azurerm_subnet.endpoint_subnet.id
private_service_connection {
name = "rdtest_blob_psc"
private_connection_resource_id = azurerm_storage_account.func.id
is_manual_connection = false
subresource_names = ["blob"]
}
}
resource "azurerm_private_endpoint" "table_endpoint" {
name = "rdtest_table_pe"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
subnet_id = azurerm_subnet.endpoint_subnet.id
private_service_connection {
name = "rdtest_table_psc"
private_connection_resource_id = azurerm_storage_account.func.id
is_manual_connection = false
subresource_names = ["table"]
}
}
resource "azurerm_private_endpoint" "queue_endpoint" {
name = "rdtest_queue_pe"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
subnet_id = azurerm_subnet.endpoint_subnet.id
private_service_connection {
name = "rdtest_queue_psc"
private_connection_resource_id = azurerm_storage_account.func.id
is_manual_connection = false
subresource_names = ["queue"]
}
}
# Create DNS A Records
resource "azurerm_private_dns_a_record" "blob_dns_a" {
name = "rdfunctestblobdns"
zone_name = azurerm_private_dns_zone.blob_dns_zone.name
resource_group_name = azurerm_resource_group.func_rg.name
ttl = 300
records = [azurerm_private_endpoint.blob_endpoint.private_service_connection.0.private_ip_address]
}
resource "azurerm_private_dns_a_record" "file_dns_a" {
name = "filedns"
zone_name = azurerm_private_dns_zone.file_dns_zone.name
resource_group_name = azurerm_resource_group.func_rg.name
ttl = 300
records = [azurerm_private_endpoint.file_endpoint.private_service_connection.0.private_ip_address]
}
resource "azurerm_private_dns_a_record" "table_dns_a" {
name = "tabledns"
zone_name = azurerm_private_dns_zone.table_dns_zone.name
resource_group_name = azurerm_resource_group.func_rg.name
ttl = 300
records = [azurerm_private_endpoint.table_endpoint.private_service_connection.0.private_ip_address]
}
resource "azurerm_private_dns_a_record" "queue_dns_a" {
name = "queuedns"
zone_name = azurerm_private_dns_zone.queue_dns_zone.name
resource_group_name = azurerm_resource_group.func_rg.name
ttl = 300
records = [azurerm_private_endpoint.queue_endpoint.private_service_connection.0.private_ip_address]
}
resource "azurerm_service_plan" "func" {
name = "func_sp"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
os_type = "Linux"
sku_name = "EP1"
}
resource "azurerm_linux_function_app" "func" {
name = "rd-test-func"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
virtual_network_subnet_id = azurerm_subnet.func_subnet.id
storage_account_name = azurerm_storage_account.func.name
storage_account_access_key = azurerm_storage_account.func.primary_access_key
service_plan_id = azurerm_service_plan.func.id
site_config {
application_stack {
node_version = 16
}
}
}
I get this error from that config
│ Error: creating Linux Function App: (Site Name "rd-test-func" / Resource Group "func"): web.AppsClient#CreateOrUpdate: Failure sending request: StatusCode=400 -- Original Error: Code="BadRequest" Message="Creation of storage file share failed with: 'The remote server returned an error: (403) Forbidden.'. Please check if the storage account is accessible." Details=[{"Message":"Creation of storage file share failed with: 'The remote server returned an error: (403) Forbidden.'. Please check if the storage account is accessible."},{"Code":"BadRequest"},{"ErrorEntity":{"Code":"BadRequest","ExtendedCode":"99022","Message":"Creation of storage file share failed with: 'The remote server returned an error: (403) Forbidden.'. Please check if the storage account is accessible.","MessageTemplate":"Creation of storage file share failed with: '{0}'. Please check if the storage account is accessible.","Parameters":["The remote server returned an error: (403) Forbidden."]}}]
│
│ with azurerm_linux_function_app.func,
│ on main.tf line 236, in resource "azurerm_linux_function_app" "func":
│ 236: resource "azurerm_linux_function_app" "func" {
│
│ creating Linux Function App: (Site Name "rd-test-func" / Resource Group "func"): web.AppsClient#CreateOrUpdate: Failure sending request: StatusCode=400 -- Original Error: Code="BadRequest" Message="Creation of storage
│ file share failed with: 'The remote server returned an error: (403) Forbidden.'. Please check if the storage account is accessible." Details=[{"Message":"Creation of storage file share failed with: 'The remote server
│ returned an error: (403) Forbidden.'. Please check if the storage account is accessible."},{"Code":"BadRequest"},{"ErrorEntity":{"Code":"BadRequest","ExtendedCode":"99022","Message":"Creation of storage file share
│ failed with: 'The remote server returned an error: (403) Forbidden.'. Please check if the storage account is accessible.","MessageTemplate":"Creation of storage file share failed with: '{0}'. Please check if the
│ storage account is accessible.","Parameters":["The remote server returned an error: (403) Forbidden."]}}]
If I take out the azurerm_storage_account_network_rules
, which I need for compliance, the config will deploy, but publishing the function fails on syncing triggers.
func azure functionapp publish rd-test-func
Getting site publishing info...
Uploading package...
Uploading 1.35 KB [###############################################################################]
Upload completed successfully.
Deployment completed successfully.
Syncing triggers...
Syncing triggers...
Syncing triggers...
Syncing triggers...
Syncing triggers...
Syncing triggers...
Error calling sync triggers (BadRequest).
If I remove virtual_network_subnet_id = azurerm_subnet.func_subnet.id
from azurerm_linux_function_app
as well the function will publish, but I need the function app connecting over the private endpoint or with network rules.
Anyone know how to get this to work?
Turns out azurerm_service_plan
needs sku_name = "B1"
or better for the vnet integration with azurerm_storage_account_network_rules
to work. The following worked with B1, S1 and P1v2.
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "func_rg" {
name = "func"
location = "eastus"
}
resource "azurerm_virtual_network" "func" {
name = "func_vnet"
address_space = ["10.0.0.0/16"]
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
}
resource "azurerm_subnet" "func_subnet" {
name = "func_subnet"
resource_group_name = azurerm_resource_group.func_rg.name
virtual_network_name = azurerm_virtual_network.func.name
address_prefixes = ["10.0.1.0/24"]
service_endpoints = ["Microsoft.Storage"]
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.Web/serverFarms"
}
}
}
resource "azurerm_storage_account" "func" {
name = "rdtestfuncsa"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_storage_account_network_rules" "func" {
storage_account_id = azurerm_storage_account.func.id
default_action = "Deny"
ip_rules = ["***.***.***.***"]
virtual_network_subnet_ids = [azurerm_subnet.func_subnet.id]
bypass = ["Metrics", "Logging", "AzureServices"]
}
resource "azurerm_service_plan" "func" {
name = "func_sp"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
os_type = "Linux"
sku_name = "B1"
}
resource "azurerm_linux_function_app" "func" {
name = "rd-test-func"
resource_group_name = azurerm_resource_group.func_rg.name
location = azurerm_resource_group.func_rg.location
virtual_network_subnet_id = azurerm_subnet.func_subnet.id
storage_account_name = azurerm_storage_account.func.name
storage_account_access_key = azurerm_storage_account.func.primary_access_key
service_plan_id = azurerm_service_plan.func.id
site_config {
application_stack {
node_version = 16
}
}
}