Search code examples
azure-storageazure-resource-managerazure-rm-templateazure-function-app

Function App with VNet Integration Failing Deployment When Setting WEBSITE_CONTENTAZUREFILECONNECTIONSTRING to Storage Behind Firewall


The following ARM template deploys: Virtual Network, Network Security Group, Storage Account, App Service Plan, Function App

When the settings for WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE are omitted (commented out) the deployment succeeds but the function app configuration shows a warning. enter image description here

When enabling the two settings, the deployment fails with a 403 Forbidden message.

New-AzResourceGroupDeployment : 17:04:05 - The deployment '20201209-170356' failed with error(s). Showing 1 out of 1 error(s).
Status Message: There was a conflict. The remote server returned an error: (403) Forbidden. (Code: BadRequest)
- There was a conflict. The remote server returned an error: (403) Forbidden. (Code:)
-  (Code:BadRequest)
-  (Code:)
CorrelationId: ec11767b-9f8f-4722-acca-e751e5c1bbe8

I have tried numerous settings on the NSG, adding service tags, allowing IPs associated with the function app. I have also tried allowing IPRules on the storage account firewall. The only setting that worked was to entirely disable the storage account firewall with 'Allow access from all networks', which is not an acceptable setting for the network.

The ARM template to demonstrate the error:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
  },
  "variables": {
    "vnetName": "vnet1a",
    "addressPrefixVnet": "10.17.0.0/20",
    "addressPrefixSubnet": "10.17.4.0/24",
    "nsgName_sb_functionapp": "[concat(variables('vnetName'), '-sb-functionapp-nsg')]",
    "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'sa1a')]",
    "appServicePlanName": "[concat(uniquestring(resourceGroup().id), 'asp1a')]",
    "functionAppName": "[concat(uniquestring(resourceGroup().id), 'asp1a')]"
  },
  "resources": [
    {
      "type": "Microsoft.Network/networkSecurityGroups",
      "apiVersion": "2019-11-01",
      "name": "[variables('nsgName_sb_functionapp')]",
      "location": "[resourceGroup().location]",
      "tags": {
        "Purpose": "Function App"
      },
      "properties": {
        "securityRules": []
      }
    }, 
    {
      "type": "Microsoft.Network/virtualNetworks",
      "apiVersion": "2019-11-01",
      "name": "[variables('vnetName')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName_sb_functionapp'))]"
      ],
      "tags": {
        "Purpose": "Debug Function App and Storage Account Connectivity"
      },
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('addressPrefixVnet')]"
          ]
        },
        "subnets": [
          {
            "name": "sb-functionapp",
            "properties": {
              "addressPrefix": "[variables('addressPrefixSubnet')]",
              "networkSecurityGroup": {
                "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName_sb_functionapp'))]"
              },
              "serviceEndpoints": [
                {
                  "service": "Microsoft.Storage",
                  "locations": [
                    "*"
                  ]
                }
              ],
              "delegations": [
                {
                  "name": "delegation",
                  "properties": {
                    "serviceName": "Microsoft.Web/serverFarms"
                  }
                }
              ],
              "privateEndpointNetworkPolicies": "Enabled",
              "privateLinkServiceNetworkPolicies": "Enabled"
            }
          }
        ],
        "enableDdosProtection": false,
        "enableVmProtection": false
      }
    },
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2019-04-01",
      "name": "[variables('storageAccountName')]",
      "location": "[resourceGroup().location]",
      "tags": {
        "Purpose": "Debug Function App and Storage Account Connectivity"
      },
      "kind": "StorageV2",
      "sku": {
        "name": "Standard_GRS",
        "tier": "Standard"
      },
      "properties": {
        "networkAcls": {
          "defaultAction": "Deny",
          "bypass": "AzureServices",
          "supportsHttpsTrafficOnly": true,
          "ipRules": [],
          "encryption": {
            "keySource": "Microsoft.Storage",
            "services": {
              "file": {
                "enabled": true
              },
              "blob": {
                "enabled": true
              }
            }
          },
          "accessTier": "Hot",
          "virtualNetworkRules": [
            {
              "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', variables('vnetName')), '/subnets/sb-functionapp')]",
              "ignoreMissingVNetServiceEndpoint": false
            }
          ]
        }
      }
    },
    {
      "type": "Microsoft.Web/serverfarms",
      "apiVersion": "2018-02-01",
      "name": "[variables('appServicePlanName')]",
      "location": "[resourceGroup().location]",
      "tags": {
        "Purpose": "Debug Function App and Storage Account Connectivity"
      },
      "sku": {
        "name": "EP1",
        "tier": "ElasticPremium",
        "size": "EP1",
        "family": "EP",
        "capacity": 1
      },
      "kind": "elastic",
      "properties": {
        "perSiteScaling": false,
        "maximumElasticWorkerCount": 20,
        "isSpot": false,
        "reserved": false,
        "isXenon": false,
        "hyperV": false,
        "targetWorkerCount": 0,
        "targetWorkerSizeId": 0
      }
    },
    {
      "type": "Microsoft.Web/sites",
      "apiVersion": "2018-11-01",
      "name": "[variables('functionAppName')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
      ],
      "tags": {
        "Purpose": "Debug Function App and Storage Account Connectivity"
      },
      "kind": "functionapp",
      "properties": {
        "enabled": true,
        "hostNameSslStates": [
          {
            "name": "[concat(variables('functionAppName'), '.azurewebsites.net')]",
            "sslState": "Disabled",
            "hostType": "Standard"
          },
          {
            "name": "[concat(variables('functionAppName'), '.scm.azurewebsites.net')]",
            "sslState": "Disabled",
            "hostType": "Repository"
          }
        ],
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
        "reserved": false,
        "isXenon": false,
        "hyperV": false,
        "scmSiteAlsoStopped": false,
        "clientAffinityEnabled": true,
        "clientCertEnabled": false,
        "hostNamesDisabled": false,
        "containerSize": 1536,
        "dailyMemoryTimeQuota": 0,
        "httpsOnly": true,
        "redundancyMode": "None",
        "siteConfig": {
          "appSettings": [
            {
              "name": "FUNCTIONS_EXTENSION_VERSION",
              "value": "~1"
            },
            {
              "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
              "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-04-01').keys[0].value)]"
            },
            {
              "name": "WEBSITE_CONTENTSHARE",
              "value": "[variables('functionAppName')]"
            },
            {
              "name": "WEBSITE_DNS_SERVER",
              "value": "168.63.129.16"
            },
            {
              "name": "WEBSITE_VNET_ROUTE_ALL",
              "value": "1"
            }
          ]
        }
      },
      "resources": [
        {
          "type": "networkConfig",
          "apiVersion": "2018-11-01",
          "name": "virtualNetwork",
          "location": "[resourceGroup().location]",
          "dependsOn": [
            "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]"
          ],
          "properties": {
            "subnetResourceId": "[concat(resourceId('Microsoft.Network/virtualNetworks', variables('vnetName')), '/subnets/sb-functionapp')]",
            "swiftSupported": true
          }
        }
      ]
    },
    {
      "type": "Microsoft.Web/sites/config",
      "apiVersion": "2018-11-01",
      "name": "[concat(variables('functionAppName'), '/web')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]"
      ],
      "tags": {
        "Purpose": "Debug Function App and Storage Account Connectivity"
      },
      "properties": {
        "numberOfWorkers": 1,
        "defaultDocuments": [
          "Default.htm",
          "Default.html",
          "Default.asp",
          "index.htm",
          "index.html",
          "iisstart.htm",
          "default.aspx",
          "index.php"
        ],
        "netFrameworkVersion": "v4.0",
        "phpVersion": "5.6",
        "requestTracingEnabled": false,
        "remoteDebuggingEnabled": false,
        "remoteDebuggingVersion": "VS2019",
        "httpLoggingEnabled": false,
        "logsDirectorySizeLimit": 35,
        "detailedErrorLoggingEnabled": false,
        "publishingUsername": "[concat('$', variables('functionAppName'))]",
        "scmType": "VSTSRM",
        "use32BitWorkerProcess": true,
        "webSocketsEnabled": false,
        "alwaysOn": false,
        "managedPipelineMode": "Integrated",
        "virtualApplications": [
          {
            "virtualPath": "/",
            "physicalPath": "site\\wwwroot",
            "preloadEnabled": true
          }
        ],
        "loadBalancing": "LeastRequests",
        "experiments": {
          "rampUpRules": [
          ]
        },
        "autoHealEnabled": false,
        "cors": {
          "allowedOrigins": [],
          "supportCredentials": false
        },
        "localMySqlEnabled": false,
        "ipSecurityRestrictions": [],
        "scmIpSecurityRestrictions": [
          {
            "ipAddress": "Any",
            "action": "Allow",
            "priority": 1,
            "name": "Allow all",
            "description": "Allow all access"
          }
        ],
        "scmIpSecurityRestrictionsUseMain": false,
        "http20Enabled": false,
        "minTlsVersion": "1.2",
        "ftpsState": "AllAllowed",
        "reservedInstanceCount": 1
      }
    }
  ]
}

Command to deploy to existing resource group:

New-AzResourceGroupDeployment -Name (Get-Date).ToString('yyyyMMdd-HHmmss') -ResourceGroupName 'Test-FunctionApp-Storage-VNet' -TemplateFile .\DebugFunctionApp.json -Verbose

I have seen the question/answer at Function App Deployment Failed - The remote server returned an error: (403) Forbidden but it doesn't solve the problem I see.


Solution

  • The solution is to add another setting named WEBSITE_CONTENTOVERVNET and to set the value to "1".

    The updated appSettings section looks like:

            "siteConfig": {
              "appSettings": [
                {
                  "name": "FUNCTIONS_EXTENSION_VERSION",
                  "value": "~1"
                },
                {
                  "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                  "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-04-01').keys[0].value)]"
                },
                {
                  "name": "WEBSITE_CONTENTOVERVNET",
                  "value": "1"
                },
                {
                  "name": "WEBSITE_CONTENTSHARE",
                  "value": "[variables('functionAppName')]"
                },
                {
                  "name": "WEBSITE_DNS_SERVER",
                  "value": "168.63.129.16"
                },
                {
                  "name": "WEBSITE_VNET_ROUTE_ALL",
                  "value": "1"
                }
              ]
            }
    

    The setting is document at https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings#website_contentovervnet

    For Premium plans only. A value of 1 enables your function app to scale when you have your storage account restricted to a virtual network. You should enable this setting when restricting your storage account to a virtual network.