I am trying to create a function app - EP1 plan with the VNET integration and private endpoint enabled and also I have enabled the private endpoint enabled for the Storage account that used for function app creation ( both the public access disabled ) however, after deployment I see the app is not accessible and it shows error in the runtime version.
Also I the function app is working fine when I enable the storage account networking to public.
I have also gonna through the similar articles , but none of them helped.https://github.com/Azure/Azure-Functions/issues/1361#issuecomment-574686347
Here is the terraform code.
resource "random_string" "suffix" {
length = 6
upper = false
special = false
numeric = true
}
resource "azurerm_resource_group" "rg" {
name = "rg-secure-funcapp"
location = "East US"
}
resource "azurerm_virtual_network" "vnet" {
name = "vnet-funcapp"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_subnet" "subnet_pe" {
name = "subnet-private-endpoints"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
service_endpoints = ["Microsoft.Storage"]
}
resource "azurerm_storage_account" "storage" {
name = "funcstorage${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = "Standard"
account_replication_type = "LRS"
allow_nested_items_to_be_public = false
min_tls_version = "TLS1_2"
network_rules {
default_action = "Deny"
virtual_network_subnet_ids = [azurerm_subnet.subnet_pe.id]
bypass = ["AzureServices"]
}
}
resource "azurerm_subnet" "subnet_vnet_integration" {
name = "subnet-vnet-integration"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.3.0/24"]
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = [
"Microsoft.Network/virtualNetworks/subnets/action",
]
}
}
}
resource "azurerm_service_plan" "asp" {
name = "asp-funcapp-ep1"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
os_type = "Windows"
sku_name = "EP1"
maximum_elastic_worker_count = 20
worker_count = 1
zone_balancing_enabled = false
}
resource "azurerm_storage_share" "share" {
name = "fileshares"
storage_account_id = azurerm_storage_account.storage.id
quota = 5120
depends_on = [azurerm_private_endpoint.storage_file]
}
resource "azurerm_private_endpoint" "storage_file" {
name = "pep-storage-file"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
subnet_id = azurerm_subnet.subnet_pe.id
private_service_connection {
name = "psc-file"
private_connection_resource_id = azurerm_storage_account.storage.id
subresource_names = ["file"]
is_manual_connection = false
}
}
resource "azurerm_windows_function_app" "func" {
name = "funcapp-${random_string.suffix.result}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
service_plan_id = azurerm_service_plan.asp.id
storage_account_name = azurerm_storage_account.storage.name
storage_account_access_key = azurerm_storage_account.storage.primary_access_key
functions_extension_version = "~4"
virtual_network_subnet_id = azurerm_subnet.subnet_vnet_integration.id
site_config {
always_on = true
vnet_route_all_enabled = true
scm_use_main_ip_restriction = true
}
app_settings = {
AzureWebJobsStorage = azurerm_storage_account.storage.shared_access_key_enabled
WEBSITE_RUN_FROM_PACKAGE = "1"
FUNCTIONS_WORKER_RUNTIME = "dotnet-isolated"
WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = azurerm_storage_account.storage.primary_connection_string
WEBSITE_CONTENTSHARE = azurerm_storage_share.share.name
WEBSITE_VNET_ROUTE_ALL = "1"
WEBSITE_DNS_SERVER = "168.63.129.16"
WEBSITE_CONTENTOVERVNET = "1"
vnetrouteallenabled = true
}
identity {
type = "SystemAssigned"
}
}
resource "azurerm_private_endpoint" "func_pe" {
name = "pe-funcapp-1"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
subnet_id = azurerm_subnet.subnet_pe.id
private_service_connection {
name = "psc-funcapp-1"
private_connection_resource_id = azurerm_windows_function_app.func.id
subresource_names = ["sites"]
is_manual_connection = false
}
}
resource "azurerm_private_dns_zone" "privatedns" {
name = "privatelink.azurewebsites.net"
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "dnslink" {
name = "dns-link"
resource_group_name = azurerm_resource_group.rg.name
private_dns_zone_name = azurerm_private_dns_zone.privatedns.name
virtual_network_id = azurerm_virtual_network.vnet.id
}
resource "azurerm_private_dns_a_record" "dnsrecord" {
name = azurerm_windows_function_app.func.name
zone_name = azurerm_private_dns_zone.privatedns.name
resource_group_name = azurerm_resource_group.rg.name
ttl = 300
records = [azurerm_private_endpoint.func_pe.private_service_connection[0].private_ip_address]
}
It seems to be that you have a problem with your DNS resolution. The "problem" behind private Endpoints ist that you need to configure the private DNS zones in the formats Microsoft needs the private DNS zone to get the handling be done in the background.
What Microsoft does is the following:
If you are not using the private DNS Zone names of Microsoft you have to do all this things by your own. My recommendation for you: Use the DNS zone names from Microsoft and let the private endpoints auto register themselves.
In your case I would suggest the following endpoints with the corresponding private DNS Zones:
Private Endpoint | Private DNS Zone Name | Sub Resource |
---|---|---|
Function App | privatelink.azurewebsites.net |
sites |
Storage Account - Blob | privatelink.blob.core.windows.net |
blob |
Storage Account - File Share | privatelink.file.core.windows.net |
file |
The official documentation for the private DNS Zones can be found at Azure Private Endpoint private DNS zone values
Private Endpoint Definition in Terraform inclusive private DNS:
resource "azurerm_private_dns_zone" "private_dns_zone_func" {
name = "privatelink.azurewebsites.net"
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "dnslink" {
name = "dns-link"
resource_group_name = azurerm_resource_group.rg.name
private_dns_zone_name = azurerm_private_dns_zone.private_dns_zone_func.name
virtual_network_id = azurerm_virtual_network.vnet.id
}
resource "azurerm_private_endpoint" "func_pe" {
name = "pe-funcapp-1"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
subnet_id = azurerm_subnet.subnet_pe.id
private_service_connection {
name = "psc-funcapp-1"
private_connection_resource_id = azurerm_windows_function_app.func.id
subresource_names = ["sites"]
is_manual_connection = false
}
private_dns_zone_group {
name = "dnszg-funcapp-1"
private_dns_zone_ids = [azurerm_private_dns_zone.private_dns_zone_func.id]
}
}
This configuration needs to be done for all the types documented in the table above. But be aware that you need to be connected to an Azure network to access your function because the public access should be disabled if you configure a private endpoint. Otherwise you can get rid of the private endpoint of the function an access the function over the public internet.
Your function should look like this with private endpoint in use:
resource "azurerm_windows_function_app" "func" {
name = "funcapp-${random_string.suffix.result}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
service_plan_id = azurerm_service_plan.asp.id
storage_account_name = azurerm_storage_account.storage.name
storage_account_access_key = azurerm_storage_account.storage.primary_access_key
functions_extension_version = "~4"
virtual_network_subnet_id = azurerm_subnet.subnet_vnet_integration.id
public_network_access_enabled = false
site_config {
always_on = true
}
app_settings = {
AzureWebJobsStorage = azurerm_storage_account.storage.shared_access_key_enabled
WEBSITE_RUN_FROM_PACKAGE = "1"
FUNCTIONS_WORKER_RUNTIME = "dotnet-isolated"
WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = azurerm_storage_account.storage.primary_connection_string
WEBSITE_CONTENTSHARE = azurerm_storage_share.share.name
vnetrouteallenabled = true
}
identity {
type = "SystemAssigned"
}
}
Youe Storage account should look like this for use with private endpoint:
resource "azurerm_storage_account" "storage" {
name = "funcstorage${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = "Standard"
account_replication_type = "LRS"
allow_nested_items_to_be_public = false
min_tls_version = "TLS1_2"
public_network_access_enabled = false
}
And one last thing I would recommend is to use IAM (RBAC) permissions to access the Storage Account. You already enabled a System assigned Identity for your function. The only thing you need to include is a role assignment at the storage account to work with files and blobs.