From e58006634523dd50a8b97a0c0ec85c6e10b302ed Mon Sep 17 00:00:00 2001 From: Mikolaj Mackowiak <7921224+miqm@users.noreply.github.com> Date: Thu, 3 Dec 2020 21:00:52 +0100 Subject: [PATCH] [Example] Function App on Consumption Plan with Custom Domain and App Serivce Managed Certificate (#971) * Function App on Consumption Plan with Custom Domain and App Serivce Managed Certificate * Used any to avoid warning; included sample in playground. * added missing newline at the end of files Co-authored-by: Alex Frankel --- .../main.bicep | 158 ++++++++++++ .../main.json | 230 ++++++++++++++++++ .../sni-enable.bicep | 11 + .../sni-enable.json | 27 ++ src/playground/src/examples.ts | 4 +- 5 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 docs/examples/301/function-app-with-custom-domain-managed-certificate/main.bicep create mode 100644 docs/examples/301/function-app-with-custom-domain-managed-certificate/main.json create mode 100644 docs/examples/301/function-app-with-custom-domain-managed-certificate/sni-enable.bicep create mode 100644 docs/examples/301/function-app-with-custom-domain-managed-certificate/sni-enable.json diff --git a/docs/examples/301/function-app-with-custom-domain-managed-certificate/main.bicep b/docs/examples/301/function-app-with-custom-domain-managed-certificate/main.bicep new file mode 100644 index 00000000000..3e5ee44d1f6 --- /dev/null +++ b/docs/examples/301/function-app-with-custom-domain-managed-certificate/main.bicep @@ -0,0 +1,158 @@ +param applicationName string +param dnsZone string { + metadata: { + description: 'Existing Azure DNS zone in target resource group' + } +} + +var location = resourceGroup().location +var componentBase = '${substring(uniqueString(resourceGroup().id), 4)}-${applicationName}' + +resource hostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = { + name: '${componentBase}-asp' + location: location + sku: { + name: 'Y1' + tier: 'Dynamic' + size: 'Y1' + family: 'Y' + capacity: 0 + } +} + +resource storage 'Microsoft.Storage/storageAccounts@2019-06-01' = { + name: '${replace(componentBase, '-', '')}st' + location: location + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } +} + +resource appInsights 'Microsoft.Insights/components@2015-05-01' = { + name: '${componentBase}-ai' + location: location + kind: 'web' + properties: { + Application_Type: 'web' + } +} +resource functionApp 'Microsoft.Web/sites@2020-06-01' = { + name: '${componentBase}-functionapp' + location: location + kind: 'functionapp' + identity: { + type: 'SystemAssigned' + } + properties: { + httpsOnly: true + serverFarmId: hostingPlan.id + clientAffinityEnabled: false + siteConfig: { + http20Enabled: true + use32BitWorkerProcess: false + ftpsState: 'FtpsOnly' + alwaysOn: false + appSettings: [ + { + name: 'FUNCTIONS_EXTENSION_VERSION' + value: '~3' + } + { + name: 'FUNCTIONS_WORKER_RUNTIME' + value: 'dotnet' + } + { + name: 'ASPNETCORE_ENVIRONMENT' + value: 'Production' + } + { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: appInsights.properties.InstrumentationKey + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: 'InstrumentationKey=${appInsights.properties.InstrumentationKey}' + } + { + name: 'AzureWebJobsStorage' + value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storage.id, storage.apiVersion).keys[0].value}' + } + { + name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' + value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storage.id, storage.apiVersion).keys[0].value}' + } + { + name: 'WEBSITE_CONTENTSHARE' + value: '${componentBase}' + } + ] + } + } +} + +resource dnsTxt 'Microsoft.Network/dnsZones/TXT@2018-05-01' = { + name: '${dnsZone}/asuid.${applicationName}' + properties: { + TTL: 3600 + TXTRecords: [ + { + value: [ + '${functionApp.properties.customDomainVerificationId}' + ] + } + ] + } +} + +resource dnsCname 'Microsoft.Network/dnsZones/CNAME@2018-05-01' = { + name: '${dnsZone}/${applicationName}' + properties: { + TTL: 3600 + CNAMERecord: { + cname: '${functionApp.name}.azurewebsites.net' + } + } +} +// Enabling Managed certificate for a webapp requires 3 steps +// 1. Add custom domain to webapp with SSL in disabled state +// 2. Generate certificate for the domain +// 3. enable SSL +// +// The last step requires deploying again Microsoft.Web/sites/hostNameBindings - and ARM template forbids this in one deplyment, therefore we need to use modules to chain this. + +resource functionAppCustomHost 'Microsoft.Web/sites/hostNameBindings@2020-06-01' = { + name: '${functionApp.name}/${applicationName}.${dnsZone}' + dependsOn: [ + dnsTxt + dnsCname + ] + properties: { + hostNameType: 'Verified' + sslState: 'Disabled' + customHostNameDnsRecordType: 'CName' + siteName: functionApp.name + } +} + +resource functionAppCustomHostCertificate 'Microsoft.Web/certificates@2020-06-01' = { + name: '${applicationName}.${dnsZone}' + location: location + dependsOn: [ + functionAppCustomHost + ] + properties: any({ + serverFarmId: hostingPlan.id + canonicalName: '${applicationName}.${dnsZone}' + }) +} + +// we need to use a module to enable sni, as ARM forbids using resource with this same type-name combination twice in one deployment. +module functionAppCustomHostEnable './sni-enable.bicep' = { + name: '${deployment().name}-${applicationName}-sni-enable' + params: { + functionAppName: functionApp.name + functionAppHostname: '${functionAppCustomHostCertificate.name}' + certificateThumbprint: functionAppCustomHostCertificate.properties.thumbprint + } +} diff --git a/docs/examples/301/function-app-with-custom-domain-managed-certificate/main.json b/docs/examples/301/function-app-with-custom-domain-managed-certificate/main.json new file mode 100644 index 00000000000..8033643f179 --- /dev/null +++ b/docs/examples/301/function-app-with-custom-domain-managed-certificate/main.json @@ -0,0 +1,230 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "applicationName": { + "type": "string" + }, + "dnsZone": { + "type": "string", + "metadata": { + "description": "Existing Azure DNS zone in target resource group" + } + } + }, + "functions": [], + "variables": { + "location": "[resourceGroup().location]", + "componentBase": "[format('{0}-{1}', substring(uniqueString(resourceGroup().id), 4), parameters('applicationName'))]" + }, + "resources": [ + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2020-06-01", + "name": "[format('{0}-asp', variables('componentBase'))]", + "location": "[variables('location')]", + "sku": { + "name": "Y1", + "tier": "Dynamic", + "size": "Y1", + "family": "Y", + "capacity": 0 + } + }, + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-06-01", + "name": "[format('{0}st', replace(variables('componentBase'), '-', ''))]", + "location": "[variables('location')]", + "kind": "StorageV2", + "sku": { + "name": "Standard_LRS" + } + }, + { + "type": "Microsoft.Insights/components", + "apiVersion": "2015-05-01", + "name": "[format('{0}-ai', variables('componentBase'))]", + "location": "[variables('location')]", + "kind": "web", + "properties": { + "Application_Type": "web" + } + }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2020-06-01", + "name": "[format('{0}-functionapp', variables('componentBase'))]", + "location": "[variables('location')]", + "kind": "functionapp", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "httpsOnly": true, + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', format('{0}-asp', variables('componentBase')))]", + "clientAffinityEnabled": false, + "siteConfig": { + "http20Enabled": true, + "use32BitWorkerProcess": false, + "ftpsState": "FtpsOnly", + "alwaysOn": false, + "appSettings": [ + { + "name": "FUNCTIONS_EXTENSION_VERSION", + "value": "~3" + }, + { + "name": "FUNCTIONS_WORKER_RUNTIME", + "value": "dotnet" + }, + { + "name": "ASPNETCORE_ENVIRONMENT", + "value": "Production" + }, + { + "name": "APPINSIGHTS_INSTRUMENTATIONKEY", + "value": "[reference(resourceId('Microsoft.Insights/components', format('{0}-ai', variables('componentBase')))).InstrumentationKey]" + }, + { + "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", + "value": "[format('InstrumentationKey={0}', reference(resourceId('Microsoft.Insights/components', format('{0}-ai', variables('componentBase')))).InstrumentationKey)]" + }, + { + "name": "AzureWebJobsStorage", + "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', format('{0}st', replace(variables('componentBase'), '-', '')), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', format('{0}st', replace(variables('componentBase'), '-', ''))), '2019-06-01').keys[0].value)]" + }, + { + "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", + "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', format('{0}st', replace(variables('componentBase'), '-', '')), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', format('{0}st', replace(variables('componentBase'), '-', ''))), '2019-06-01').keys[0].value)]" + }, + { + "name": "WEBSITE_CONTENTSHARE", + "value": "[variables('componentBase')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', format('{0}-ai', variables('componentBase')))]", + "[resourceId('Microsoft.Web/serverfarms', format('{0}-asp', variables('componentBase')))]", + "[resourceId('Microsoft.Storage/storageAccounts', format('{0}st', replace(variables('componentBase'), '-', '')))]" + ] + }, + { + "type": "Microsoft.Network/dnsZones/TXT", + "apiVersion": "2018-05-01", + "name": "[format('{0}/asuid.{1}', parameters('dnsZone'), parameters('applicationName'))]", + "properties": { + "TTL": 3600, + "TXTRecords": [ + { + "value": [ + "[reference(resourceId('Microsoft.Web/sites', format('{0}-functionapp', variables('componentBase')))).customDomainVerificationId]" + ] + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', format('{0}-functionapp', variables('componentBase')))]" + ] + }, + { + "type": "Microsoft.Network/dnsZones/CNAME", + "apiVersion": "2018-05-01", + "name": "[format('{0}/{1}', parameters('dnsZone'), parameters('applicationName'))]", + "properties": { + "TTL": 3600, + "CNAMERecord": { + "cname": "[format('{0}.azurewebsites.net', format('{0}-functionapp', variables('componentBase')))]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', format('{0}-functionapp', variables('componentBase')))]" + ] + }, + { + "type": "Microsoft.Web/sites/hostNameBindings", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}.{2}', format('{0}-functionapp', variables('componentBase')), parameters('applicationName'), parameters('dnsZone'))]", + "properties": { + "hostNameType": "Verified", + "sslState": "Disabled", + "customHostNameDnsRecordType": "CName", + "siteName": "[format('{0}-functionapp', variables('componentBase'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/dnsZones/CNAME', split(format('{0}/{1}', parameters('dnsZone'), parameters('applicationName')), '/')[0], split(format('{0}/{1}', parameters('dnsZone'), parameters('applicationName')), '/')[1])]", + "[resourceId('Microsoft.Network/dnsZones/TXT', split(format('{0}/asuid.{1}', parameters('dnsZone'), parameters('applicationName')), '/')[0], split(format('{0}/asuid.{1}', parameters('dnsZone'), parameters('applicationName')), '/')[1])]", + "[resourceId('Microsoft.Web/sites', format('{0}-functionapp', variables('componentBase')))]" + ] + }, + { + "type": "Microsoft.Web/certificates", + "apiVersion": "2020-06-01", + "name": "[format('{0}.{1}', parameters('applicationName'), parameters('dnsZone'))]", + "location": "[variables('location')]", + "properties": { + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', format('{0}-asp', variables('componentBase')))]", + "canonicalName": "[format('{0}.{1}', parameters('applicationName'), parameters('dnsZone'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/hostNameBindings', split(format('{0}/{1}.{2}', format('{0}-functionapp', variables('componentBase')), parameters('applicationName'), parameters('dnsZone')), '/')[0], split(format('{0}/{1}.{2}', format('{0}-functionapp', variables('componentBase')), parameters('applicationName'), parameters('dnsZone')), '/')[1])]", + "[resourceId('Microsoft.Web/serverfarms', format('{0}-asp', variables('componentBase')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2019-10-01", + "name": "[format('{0}-{1}-sni-enable', deployment().name, parameters('applicationName'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "functionAppName": { + "value": "[format('{0}-functionapp', variables('componentBase'))]" + }, + "functionAppHostname": { + "value": "[format('{0}.{1}', parameters('applicationName'), parameters('dnsZone'))]" + }, + "certificateThumbprint": { + "value": "[reference(resourceId('Microsoft.Web/certificates', format('{0}.{1}', parameters('applicationName'), parameters('dnsZone')))).thumbprint]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "functionAppName": { + "type": "string" + }, + "functionAppHostname": { + "type": "string" + }, + "certificateThumbprint": { + "type": "string" + } + }, + "functions": [], + "resources": [ + { + "type": "Microsoft.Web/sites/hostNameBindings", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('functionAppName'), parameters('functionAppHostname'))]", + "properties": { + "sslState": "SniEnabled", + "thumbprint": "[parameters('certificateThumbprint')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', format('{0}-functionapp', variables('componentBase')))]", + "[resourceId('Microsoft.Web/certificates', format('{0}.{1}', parameters('applicationName'), parameters('dnsZone')))]" + ] + } + ] +} \ No newline at end of file diff --git a/docs/examples/301/function-app-with-custom-domain-managed-certificate/sni-enable.bicep b/docs/examples/301/function-app-with-custom-domain-managed-certificate/sni-enable.bicep new file mode 100644 index 00000000000..c56557b367f --- /dev/null +++ b/docs/examples/301/function-app-with-custom-domain-managed-certificate/sni-enable.bicep @@ -0,0 +1,11 @@ +param functionAppName string +param functionAppHostname string +param certificateThumbprint string + +resource functionAppCustomHostEnable 'Microsoft.Web/sites/hostNameBindings@2020-06-01' = { + name: '${functionAppName}/${functionAppHostname}' + properties: { + sslState: 'SniEnabled' + thumbprint: certificateThumbprint + } +} diff --git a/docs/examples/301/function-app-with-custom-domain-managed-certificate/sni-enable.json b/docs/examples/301/function-app-with-custom-domain-managed-certificate/sni-enable.json new file mode 100644 index 00000000000..c3277b1a26c --- /dev/null +++ b/docs/examples/301/function-app-with-custom-domain-managed-certificate/sni-enable.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "functionAppName": { + "type": "string" + }, + "functionAppHostname": { + "type": "string" + }, + "certificateThumbprint": { + "type": "string" + } + }, + "functions": [], + "resources": [ + { + "type": "Microsoft.Web/sites/hostNameBindings", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('functionAppName'), parameters('functionAppHostname'))]", + "properties": { + "sslState": "SniEnabled", + "thumbprint": "[parameters('certificateThumbprint')]" + } + } + ] +} \ No newline at end of file diff --git a/src/playground/src/examples.ts b/src/playground/src/examples.ts index 08c09826716..043d09bb18b 100644 --- a/src/playground/src/examples.ts +++ b/src/playground/src/examples.ts @@ -69,6 +69,7 @@ import example_201_wvd_backplane_with_network_and_storage_and_monitoring from '. import example_301_modules_vwan_to_vnet_s2s_with_fw from '../../../docs/examples/301/modules-vwan-to-vnet-s2s-with-fw/main.bicep' import example_301_nested_vms_in_virtual_network from '../../../docs/examples/301/nested-vms-in-virtual-network/main.bicep' import example_301_servicebus_namespace_vnet from '../../../docs/examples/301/servicebus-namespace-vnet/main.bicep' +import example_301_function_app_with_custom_domain_managed_certificate from '../../../docs/examples/301/function-app-with-custom-domain-managed-certificate/main.bicep' export const examples = { 'blank': '', @@ -142,5 +143,6 @@ export const examples = { '201/wvd-backplane-with-network-and-storage-and-monitoring': example_201_wvd_backplane_with_network_and_storage_and_monitoring, '301/modules-vwan-to-vnet-s2s-with-fw': example_301_modules_vwan_to_vnet_s2s_with_fw, '301/nested-vms-in-virtual-network': example_301_nested_vms_in_virtual_network, - '301/servicebus-namespace-vnet': example_301_servicebus_namespace_vnet + '301/servicebus-namespace-vnet': example_301_servicebus_namespace_vnet, + '301/function-app-with-custom-domain-managed-certificate': example_301_function_app_with_custom_domain_managed_certificate }