├── images └── appmodernized-with-appservice-well-architected-accelerator.jpg ├── nestedtemplates ├── monitoring.bicep ├── networkwatcher.bicep ├── azuresql.bicep ├── privateEndpoints.bicep ├── networking.bicep ├── keyvault.bicep ├── applicationGW_https.bicep └── appservice.bicep ├── metadata.json ├── README.md ├── main.bicep └── LICENSE /images/appmodernized-with-appservice-well-architected-accelerator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KietNhiTran/appmodernized-with-appservice-well-architected-accelerator/HEAD/images/appmodernized-with-appservice-well-architected-accelerator.jpg -------------------------------------------------------------------------------- /nestedtemplates/monitoring.bicep: -------------------------------------------------------------------------------- 1 | @description('The location in which all resources should be deployed.') 2 | param location string = resourceGroup().location 3 | 4 | @description('Log Analytics Workspace name') 5 | param wsName string = 'laws${uniqueString(resourceGroup().id)}' 6 | 7 | 8 | resource logWorkspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { 9 | location: location 10 | name: wsName 11 | properties: { 12 | sku: { 13 | name: 'PerGB2018' 14 | } 15 | retentionInDays: 120 16 | features: { 17 | searchVersion: 1 18 | legacy: 0 19 | enableLogAccessUsingOnlyResourcePermissions: true 20 | } 21 | } 22 | } 23 | 24 | 25 | output lawsName_output string = logWorkspace.name 26 | output lawsId_output string = logWorkspace.id 27 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://aka.ms/azure-quickstart-templates-metadata-schema#", 3 | "itemDisplayName": "Web app meets most of the well-architected best practices", 4 | "description": "Deploys an Azure App Services web app with zone redundance, regional virtual network injection to allow the app to access an Azure SQL DB, keyvault over a private endpoint, restrict access through Application Gateway WAF V2 only, application perfiormance monitoring is turned on with application insights, central log management through log analytics workspace with network flow log monitoring.", 5 | "summary": "Web app meets most of well-architected best practices", 6 | "githubUsername": "kietnhitran", 7 | "dateUpdated": "2021-05-12", 8 | "type": "QuickStart", 9 | "environments": [ 10 | "AzureCloud" 11 | ] 12 | } -------------------------------------------------------------------------------- /nestedtemplates/networkwatcher.bicep: -------------------------------------------------------------------------------- 1 | 2 | @description('Specifies the location for all the resources.') 3 | param location string 4 | 5 | @description('Network flow log retention in days') 6 | param flowLogsStorageRetentionDays int = 90 7 | 8 | @description('Network security group') 9 | param nsgId string 10 | 11 | @description('NSG name') 12 | param nsgName string 13 | 14 | // @description('Networkflow log storage account identity') 15 | // param nflStorageAccountId string 16 | 17 | @description('name of log analytics workspace') 18 | param lawsId string 19 | 20 | @description('Specifies the network flow log storage account where the log will be stored') 21 | param nflStorageAccountName string 22 | 23 | @description('Storage Account type') 24 | @allowed([ 25 | 'Standard_LRS' 26 | 'Standard_GRS' 27 | 'Standard_ZRS' 28 | ]) 29 | param storageAccountType string = 'Standard_LRS' 30 | 31 | 32 | var networkWatcherName = 'NetworkWatcher_${location}' 33 | var networkFlowLogName = '${nsgName}FlowLog${uniqueString(resourceGroup().id)}' 34 | 35 | resource nflStorageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = { 36 | kind: 'StorageV2' 37 | location: location 38 | name: nflStorageAccountName 39 | sku: { 40 | name: storageAccountType 41 | } 42 | } 43 | 44 | resource nfl_resource 'Microsoft.Network/networkWatchers/flowLogs@2021-05-01' = { 45 | name: '${networkWatcherName}/${networkFlowLogName}' 46 | location: location 47 | properties: { 48 | storageId: nflStorageAccount.id 49 | targetResourceId: nsgId 50 | enabled: true 51 | flowAnalyticsConfiguration: { 52 | networkWatcherFlowAnalyticsConfiguration: { 53 | enabled: true 54 | trafficAnalyticsInterval: 60 55 | workspaceResourceId: lawsId 56 | } 57 | } 58 | format: { 59 | type: 'JSON' 60 | version: 2 61 | } 62 | retentionPolicy: { 63 | days: flowLogsStorageRetentionDays 64 | enabled: true 65 | } 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /nestedtemplates/azuresql.bicep: -------------------------------------------------------------------------------- 1 | @description('Specifies the location for all the resources.') 2 | param location string 3 | 4 | @description('Azure SQL DB server name') 5 | param sqlServerName string = 'sqlServer${uniqueString(resourceGroup().id)}' 6 | 7 | @description('Azure SQL DB server name') 8 | param sqlDatabaseName string = 'db001' 9 | 10 | @description('SQL server admin user name') 11 | param sqlServerAdmin string 12 | 13 | @description('Sql Server admin password') 14 | param sqlServerPassword string 15 | 16 | @description('database collation') 17 | param databaseCollation string = 'SQL_Latin1_General_CP1_CI_AS' 18 | 19 | @description('Enable zone redudant') 20 | param enableZoneRedundant bool 21 | 22 | @description('name of log analytics workspace') 23 | param lawsName string 24 | 25 | @description('Sku of the Database') 26 | param dbSKU string = 'GP_Gen5_2' 27 | 28 | 29 | resource azureSQL 'Microsoft.Sql/servers@2021-05-01-preview' = { 30 | location: location 31 | name: sqlServerName 32 | properties: { 33 | administratorLogin: sqlServerAdmin 34 | administratorLoginPassword: sqlServerPassword 35 | publicNetworkAccess: 'Disabled' 36 | } 37 | 38 | } 39 | 40 | resource azureSQLDB 'Microsoft.Sql/servers/databases@2021-05-01-preview' = { 41 | location: location 42 | name: sqlDatabaseName 43 | parent: azureSQL 44 | properties: { 45 | collation: databaseCollation 46 | sampleName: 'AdventureWorksLT' 47 | zoneRedundant: enableZoneRedundant 48 | } 49 | sku: { 50 | name: dbSKU 51 | } 52 | } 53 | 54 | // setup diagnostic setting 55 | resource laws_resource 'Microsoft.OperationalInsights/workspaces@2021-06-01' existing = { 56 | name: lawsName 57 | } 58 | 59 | resource keyvaultDiagnostics_resource 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 60 | name: '${sqlServerName}-${sqlDatabaseName}-AllDiagnostic' 61 | scope: azureSQLDB 62 | properties: { 63 | workspaceId: laws_resource.id 64 | logs: [ 65 | { 66 | categoryGroup: 'allLogs' 67 | enabled: true 68 | } 69 | ] 70 | metrics: [ 71 | { 72 | category: 'InstanceAndAppAdvanced' 73 | enabled: true 74 | } 75 | ] 76 | } 77 | } 78 | 79 | output sqlName_output string = azureSQL.name 80 | output sqlServerId_output string = azureSQL.id 81 | -------------------------------------------------------------------------------- /nestedtemplates/privateEndpoints.bicep: -------------------------------------------------------------------------------- 1 | @description('Specifies the location for all the resources.') 2 | param location string 3 | 4 | @description('Private endpoint Name') 5 | param privateEndpointName string 6 | 7 | @description('private endpoint connection name') 8 | param privateEndpointConnectionName string = '${privateEndpointName}-pep-${uniqueString(resourceGroup().id)}' 9 | 10 | @description('ID of Service resource which privdes endpoint service') 11 | param privateEndpointServiceId string 12 | 13 | @description('Private endpoint group type ID') 14 | param groupType string 15 | 16 | @description('Subnet which hosts the private endpoint') 17 | param privateEndpointSubnetName string 18 | 19 | @description('Private DNS zone name') 20 | param privateDNSZoneName string 21 | 22 | @description('Virtual Network name') 23 | param virtualNetworkName string 24 | 25 | @description('Private DNS zone name') 26 | param privateDNSZoneLinkName string = '${privateDNSZoneName}/${privateDNSZoneName}-${virtualNetworkName}-link' 27 | 28 | @description('FQDN of the service which provides endpoint sevice.') 29 | param privateEndpointFQDN string 30 | 31 | var privateDnsZoneGroupName_var = '${privateEndpointName}/${groupType}PrivateDnsZoneGroup' 32 | 33 | resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' existing = { 34 | name: virtualNetworkName 35 | } 36 | 37 | resource subnet 'Microsoft.Network/virtualNetworks/subnets@2021-05-01' existing = { 38 | name: privateEndpointSubnetName 39 | parent: virtualNetwork 40 | } 41 | 42 | resource privateEndpoint_resource 'Microsoft.Network/privateEndpoints@2020-04-01' = { 43 | name: privateEndpointName 44 | location: location 45 | properties: { 46 | privateLinkServiceConnections: [ 47 | { 48 | name: privateEndpointConnectionName 49 | properties: { 50 | privateLinkServiceId: privateEndpointServiceId 51 | groupIds: [ 52 | groupType 53 | ] 54 | } 55 | } 56 | ] 57 | subnet: { 58 | id: subnet.id 59 | } 60 | customDnsConfigs: [ 61 | { 62 | fqdn: privateEndpointFQDN 63 | } 64 | ] 65 | } 66 | dependsOn: [ 67 | subnet 68 | ] 69 | } 70 | 71 | resource privateDNSZone_resource 'Microsoft.Network/privateDnsZones@2020-06-01' = { 72 | name: privateDNSZoneName 73 | location: 'global' 74 | properties: { 75 | } 76 | } 77 | 78 | resource privateDNSLink_resource 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 79 | name: privateDNSZoneLinkName 80 | location: 'global' 81 | properties: { 82 | registrationEnabled: false 83 | virtualNetwork: { 84 | id: virtualNetwork.id 85 | } 86 | } 87 | dependsOn: [ 88 | privateDNSZone_resource 89 | virtualNetwork 90 | ] 91 | } 92 | 93 | resource privateDnsZoneGroup_resource 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2020-03-01' = { 94 | name: privateDnsZoneGroupName_var 95 | properties: { 96 | privateDnsZoneConfigs: [ 97 | { 98 | name: 'dnsConfig' 99 | properties: { 100 | privateDnsZoneId: privateDNSZone_resource.id 101 | } 102 | } 103 | ] 104 | } 105 | dependsOn: [ 106 | privateEndpoint_resource 107 | ] 108 | } 109 | 110 | output privateEndpointFQDN_output string = privateEndpointFQDN 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Application Modernization with App Services and well-architected guidance on Azure 2 | 3 | This is not just another Quickstart template to spin up App Server nor demonstrate vnet integration. It aims to provide a full accerelator template for App Sevices based application modernization which meets the well architected framework guidance of Azure. 4 | 5 | ## Architecture 6 | ![Architecture diagram](https://raw.githubusercontent.com/KietNhiTran/appmodernized-with-appservice-well-architected-accelerator/main/images/appmodernized-with-appservice-well-architected-accelerator.jpg) 7 | 8 | ## How to deploy 9 | The project used bicep as main language for deployment. 10 | 11 | To learn about bicep, please visit [bicep documentation.](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview) 12 | 13 | ```console 14 | az deployment sub create --name --template-file main.bicep --location --parameters resourceGroupName='' location='' sqlServerAdmin='' sqlServerPassword='' enableZoneRedundant='' 15 | ``` 16 | 17 | Example deployement: 18 | ```console 19 | az deployment sub create --name prodeatusdeployment --template-file main.bicep --location eastus --parameters resourceGroupName='prodtemplate03-rg' location='eastus' sqlServerAdmin='sqladmin' sqlServerPassword='Abcd12345678' enableZoneRedundant='true' 20 | ``` 21 | 22 | ## Features in this accelerator 23 | - Web application inbound traffic is protected via [Application Gateway web firewall OWASP 3.2 prebuilt rule set](https://docs.microsoft.com/en-us/azure/web-application-firewall/ag/application-gateway-crs-rulegroups-rules?tabs=owasp32), SSL termination and using https. 24 | - App Services is provisioned with following best practices: 25 | - [zone redundance is applied which helps overcome datacenter failured ](https://docs.microsoft.com/en-us/azure/app-service/how-to-zone-redundancy) 26 | - [Health check endpoint is configured](https://docs.microsoft.com/en-us/azure/app-service/monitor-instances-health-check) 27 | - [Access restriction is turned and only allow traffice from Application Gateway](https://docs.microsoft.com/en-us/azure/app-service/networking/app-gateway-with-service-endpoints#integration-with-app-service-multi-tenant) 28 | - Using keyvault to store sensitive configuration 29 | - Implement secure access to backend database and keyvault via [regional VNet integration and private endpoints](https://docs.microsoft.com/en-us/azure/app-service/configure-vnet-integration-enable) 30 | - [Performance monitoring via Application Insight](https://docs.microsoft.com/en-us/azure/app-service/monitor-app-service) 31 | - The accelerator also demonstrate how to upload a self-signed certificate to key vault during deployment and how to refer to secret in keyvault using deployment template 32 | - The database & key vault are blocked internet access and only accessible via private endpoint. 33 | - All resources are turn on diagnostics setting to centrally connect platform log and metrics to log analytics workspace. 34 | - Network security group flow log is created under region network watcher and turn on traffice analysis through log analytics workspace. 35 | - Using managed identity to grant access and role assignment. 36 | 37 | ## Planned features 38 | - Prebiult dashboard with critical metrics to the app and database. 39 | - Apply more security best practices like: DDoS, defenders. 40 | - CI/CD to demo a IaC deployment vis github action. 41 | - you name it via 'issue' :-) 42 | 43 | # Contribution Guide 44 | 1. Fork this repo 45 | 2. Clone the repor locally 46 | 3. Create feature branch 47 | 4. Commit your change to feature branch 48 | 5. Push your change 49 | 6. Create a pull request to this repo 50 | 51 | # Feedback 52 | Feel free to request new feature and post your idea in issue list. 53 | 54 | -------------------------------------------------------------------------------- /nestedtemplates/networking.bicep: -------------------------------------------------------------------------------- 1 | 2 | @description('Specifies the location for all the resources.') 3 | param location string = resourceGroup().location 4 | 5 | @description('Specifies the name of the virtual network hosting the virtual machine.') 6 | param virtualNetworkName string = 'vnet${uniqueString(resourceGroup().id)}' 7 | 8 | @description('Specifies the address prefix of the virtual network hosting the virtual machine.') 9 | param virtualNetworkAddressPrefix string = '10.0.0.0/16' 10 | 11 | @description('Specifies the name of the subnet hosting the virtual machine.') 12 | param privateEnpointSubnetName string = 'privateEnpointSubnet${uniqueString(resourceGroup().id)}' 13 | 14 | @description('Application subnet name') 15 | param appServiceSubnetName string = 'applicationSubnet${uniqueString(resourceGroup().id)}' 16 | 17 | @description('Application Gateway service endpoint subnet name') 18 | param appGWSubnetName string = 'appGWServiceEndpointSubnet${uniqueString(resourceGroup().id)}' 19 | 20 | @description('Specifies the address prefix of the subnet hosting the Keyvault private endpoint.') 21 | param kvPrivateEnpointSubnetAddressPrefix string = '10.0.0.0/24' 22 | 23 | @description('Specifies the address prefix of the subnet hosting app service netwroking integration.') 24 | param applicationSubnetSubnetAddressPrefix string = '10.0.1.0/24' 25 | 26 | @description('Specifies the address prefix of the subnet hosting the application gateway service endpoint.') 27 | param applicationGWSubnetSubnetAddressPrefix string = '10.0.2.0/24' 28 | 29 | @description('name of log analytics workspace') 30 | param lawsName string 31 | 32 | var nsgName_var = '${virtualNetworkName}-Nsg' 33 | 34 | resource nsg_resource 'Microsoft.Network/networkSecurityGroups@2019-08-01' = { 35 | name: nsgName_var 36 | location: location 37 | properties: { 38 | } 39 | } 40 | 41 | resource virtualNetwork_resource 'Microsoft.Network/virtualNetworks@2019-11-01' = { 42 | name: virtualNetworkName 43 | location: location 44 | properties: { 45 | addressSpace: { 46 | addressPrefixes: [ 47 | virtualNetworkAddressPrefix 48 | ] 49 | } 50 | subnets: [ 51 | { 52 | name: privateEnpointSubnetName 53 | properties: { 54 | addressPrefix: kvPrivateEnpointSubnetAddressPrefix 55 | networkSecurityGroup: { 56 | id: nsg_resource.id 57 | } 58 | privateEndpointNetworkPolicies: 'Disabled' 59 | privateLinkServiceNetworkPolicies: 'Enabled' 60 | } 61 | } 62 | { 63 | name: appServiceSubnetName 64 | properties: { 65 | addressPrefix: applicationSubnetSubnetAddressPrefix 66 | delegations: [ 67 | { 68 | name: 'delegatedToAppService' 69 | properties: { 70 | serviceName: 'Microsoft.Web/serverFarms' 71 | } 72 | } 73 | ] 74 | privateEndpointNetworkPolicies: 'Enabled' 75 | networkSecurityGroup: { 76 | id: nsg_resource.id 77 | } 78 | } 79 | } 80 | { 81 | name: appGWSubnetName 82 | properties: { 83 | addressPrefix: applicationGWSubnetSubnetAddressPrefix 84 | privateEndpointNetworkPolicies: 'Enabled' 85 | serviceEndpoints: [ 86 | { 87 | service: 'Microsoft.Web' 88 | locations: [ 89 | '*' 90 | ] 91 | } 92 | ] 93 | } 94 | } 95 | ] 96 | } 97 | } 98 | 99 | // setup diagnostic setting 100 | resource laws_resource 'Microsoft.OperationalInsights/workspaces@2021-06-01' existing = { 101 | name: lawsName 102 | } 103 | 104 | resource virtualNetworkDiagnostic_resource 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 105 | name: '${virtualNetworkName}AllDiagnostic' 106 | scope: virtualNetwork_resource 107 | properties: { 108 | workspaceId: laws_resource.id 109 | logs: [ 110 | { 111 | categoryGroup: 'allLogs' 112 | enabled: true 113 | } 114 | ] 115 | metrics: [ 116 | { 117 | category: 'AllMetrics' 118 | enabled: true 119 | } 120 | ] 121 | } 122 | } 123 | 124 | resource nsg_daignostics_resource 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 125 | name: '${nsgName_var}AllDiagnostic' 126 | scope: nsg_resource 127 | properties: { 128 | workspaceId: laws_resource.id 129 | logs: [ 130 | { 131 | categoryGroup: 'allLogs' 132 | enabled: true 133 | } 134 | ] 135 | } 136 | } 137 | 138 | output virtualNetworkName_output string = virtualNetwork_resource.name 139 | output privateEnpointSubnetName_output string = privateEnpointSubnetName 140 | output applicationGWServiceEnpointSubnetName_output string = appGWSubnetName 141 | output applicationSubnetName_output string = appServiceSubnetName 142 | output nsgName_output string = nsgName_var 143 | output nsgId_output string = nsg_resource.id 144 | // output nflStorageAccountId_output string = nflStorageAccount.id 145 | 146 | -------------------------------------------------------------------------------- /nestedtemplates/keyvault.bicep: -------------------------------------------------------------------------------- 1 | @description('Specifies the name of the key vault.') 2 | param keyVaultName string = 'vault${uniqueString(resourceGroup().id)}' 3 | 4 | @description('Specifies the location for all the resources.') 5 | param location string = resourceGroup().location 6 | 7 | @description('Specifies the Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. Get it by using Get-AzSubscription cmdlet.') 8 | param tenantId string = subscription().tenantId 9 | 10 | @description('Name of veritual network where the private endpoint will be created') 11 | param virtualNetworkName string 12 | 13 | @description('Name of veritual network where the private endpoint will be created') 14 | param endpointSubnetName string 15 | 16 | @allowed([ 17 | 'standard' 18 | 'premium' 19 | ]) 20 | @description('Specifies whether the key vault is a standard vault or a premium vault.') 21 | param skuName string = 'standard' 22 | 23 | @allowed([ 24 | true 25 | false 26 | ]) 27 | @description('Specifies whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault.') 28 | param enabledForDeployment bool = true 29 | 30 | @allowed([ 31 | true 32 | false 33 | ]) 34 | @description('Specifies whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys.') 35 | param enabledForDiskEncryption bool = true 36 | 37 | @allowed([ 38 | true 39 | false 40 | ]) 41 | @description('Specifies whether Azure Resource Manager is permitted to retrieve secrets from the key vault.') 42 | param enabledForTemplateDeployment bool = true 43 | 44 | @allowed([ 45 | true 46 | false 47 | ]) 48 | @description('Specifies whether the \'soft delete\' functionality is enabled for this key vault. If it\'s not set to any value(true or false) when creating new key vault, it will be set to true by default. Once set to true, it cannot be reverted to false.') 49 | param enableSoftDelete bool = true 50 | 51 | @description('Specifies the softDelete data retention days. It accepts >=7 and <=90.') 52 | param softDeleteRetentionInDays int = 90 53 | 54 | @allowed([ 55 | true 56 | false 57 | ]) 58 | @description('Controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored (warning: this is a preview feature). When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. If null or not specified, the vault is created with the default value of false. Note that management actions are always authorized with RBAC.') 59 | param enableRbacAuthorization bool = false 60 | 61 | // @description('Specifies the name of the private link to key vault.') 62 | // param keyVaultPrivateEndpointName string = '${keyVaultName}KeyVaultPrivateEndpoint' 63 | 64 | @description('Specifies all secrets {"secretName":"","secretValue":""} wrapped in a secure object. This is for testing purpose only') 65 | param secretsArray array = [ 66 | { 67 | secretName: 'secret1' 68 | secretValue: 'value1' 69 | } 70 | { 71 | secretName: 'secret2' 72 | secretValue: 'value2' 73 | } 74 | { 75 | secretName: 'secret3' 76 | secretValue: 'value3' 77 | } 78 | ] 79 | 80 | @description('SSL certificate to be uploaded to keyvault') 81 | param certsArray array 82 | 83 | @description('name of log analytics workspace') 84 | param lawsName string 85 | 86 | // var keyVaultPublicDNSZoneForwarder = ((toLower(environment().name) == 'azureusgovernment') ? '.vaultcore.usgovcloudapi.net' : '.vaultcore.azure.net') 87 | // var keyVaultPrivateDnsZoneName_var = 'privatelink${keyVaultPublicDNSZoneForwarder}' 88 | // var keyVaultPrivateEndpointGroupName = 'vault' 89 | // var keyVaultPrivateDnsZoneGroupName_var = '${keyVaultPrivateEndpointName}/${keyVaultPrivateEndpointGroupName}PrivateDnsZoneGroup' 90 | 91 | resource keyVault_resource 'Microsoft.KeyVault/vaults@2019-09-01' = { 92 | name: keyVaultName 93 | location: location 94 | 95 | properties: { 96 | tenantId: tenantId 97 | sku: { 98 | name: skuName 99 | family: 'A' 100 | } 101 | enabledForDeployment: enabledForDeployment 102 | enabledForTemplateDeployment: enabledForTemplateDeployment 103 | enabledForDiskEncryption: enabledForDiskEncryption 104 | enableSoftDelete: enableSoftDelete 105 | softDeleteRetentionInDays: softDeleteRetentionInDays 106 | enableRbacAuthorization: enableRbacAuthorization 107 | networkAcls: { 108 | bypass: 'AzureServices' 109 | defaultAction: 'Deny' 110 | ipRules: [] 111 | virtualNetworkRules: [] 112 | } 113 | accessPolicies: [ 114 | // { 115 | // tenantId: tenantId 116 | // objectId: reference(vmId, '2019-12-01', 'Full').identity.principalId 117 | // permissions: { 118 | // keys: keysPermissions 119 | // secrets: secretsPermissions 120 | // certificates: certificatesPermissions 121 | // } 122 | // } 123 | ] 124 | } 125 | } 126 | 127 | resource keyVaultName_secretsArray_secretName 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = [for item in secretsArray: { 128 | name: '${keyVaultName}/${item.secretName}' 129 | properties: { 130 | value: item.secretValue 131 | } 132 | dependsOn: [ 133 | keyVault_resource 134 | ] 135 | }] 136 | 137 | resource keyvault_cetificate_sslcert 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = [for item in certsArray: { 138 | name: '${keyVaultName}/${item.name}' 139 | properties: { 140 | value: item.value 141 | } 142 | dependsOn: [ 143 | keyVault_resource 144 | ] 145 | }] 146 | 147 | 148 | resource virtualNetwork_resource 'Microsoft.Network/virtualNetworks@2021-03-01' existing = { 149 | name: virtualNetworkName 150 | } 151 | 152 | resource endpointSubnet_resource 'Microsoft.Network/virtualNetworks/subnets@2021-03-01' existing = { 153 | name: endpointSubnetName 154 | parent: virtualNetwork_resource 155 | } 156 | 157 | // resource keyVaultPrivateDnsZone_resource 'Microsoft.Network/privateDnsZones@2020-06-01' = { 158 | // name: keyVaultPrivateDnsZoneName_var 159 | // location: 'global' 160 | // properties: { 161 | // } 162 | // } 163 | 164 | // resource keyVaultPrivateDnsZoneName_link_to_virtualNetworkName 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2018-09-01' = { 165 | // name: '${keyVaultPrivateDnsZone_resource.name}/link_to_${toLower(virtualNetworkName)}' 166 | // location: 'global' 167 | // properties: { 168 | // registrationEnabled: false 169 | // virtualNetwork: { 170 | // id: virtualNetwork_resource.id 171 | // } 172 | // } 173 | // dependsOn: [ 174 | // virtualNetwork_resource 175 | // ] 176 | // } 177 | 178 | // resource keyVaultPrivateEndpoint_resource 'Microsoft.Network/privateEndpoints@2020-04-01' = { 179 | // name: keyVaultPrivateEndpointName 180 | // location: location 181 | // properties: { 182 | // privateLinkServiceConnections: [ 183 | // { 184 | // name: keyVaultPrivateEndpointName 185 | // properties: { 186 | // privateLinkServiceId: keyVault_resource.id 187 | // groupIds: [ 188 | // keyVaultPrivateEndpointGroupName 189 | // ] 190 | // } 191 | // } 192 | // ] 193 | // subnet: { 194 | // id: endpointSubnet_resource.id 195 | // } 196 | // customDnsConfigs: [ 197 | // { 198 | // fqdn: '${keyVaultName}${keyVaultPublicDNSZoneForwarder}' 199 | // } 200 | // ] 201 | // } 202 | // dependsOn: [ 203 | // endpointSubnet_resource 204 | // ] 205 | // } 206 | 207 | // resource keyVaultPrivateDnsZoneGroupName 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2020-03-01' = { 208 | // name: keyVaultPrivateDnsZoneGroupName_var 209 | // properties: { 210 | // privateDnsZoneConfigs: [ 211 | // { 212 | // name: 'dnsConfig' 213 | // properties: { 214 | // privateDnsZoneId: keyVaultPrivateDnsZone_resource.id 215 | // } 216 | // } 217 | // ] 218 | // } 219 | // dependsOn: [ 220 | // keyVault_resource 221 | // keyVaultPrivateEndpoint_resource 222 | // ] 223 | // } 224 | 225 | // setup diagnostic setting 226 | resource laws_resource 'Microsoft.OperationalInsights/workspaces@2021-06-01' existing = { 227 | name: lawsName 228 | } 229 | 230 | resource keyvaultDiagnostics_resource 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 231 | name: '${keyVaultName}AllDiagnostic' 232 | scope: keyVault_resource 233 | properties: { 234 | workspaceId: laws_resource.id 235 | logs: [ 236 | { 237 | categoryGroup: 'allLogs' 238 | enabled: true 239 | } 240 | ] 241 | metrics: [ 242 | { 243 | category: 'AllMetrics' 244 | enabled: true 245 | } 246 | ] 247 | } 248 | } 249 | 250 | output keyVaultName_output string = keyVaultName 251 | output keyVaultId_output string = keyVault_resource.id 252 | 253 | -------------------------------------------------------------------------------- /main.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'subscription' 2 | 3 | @description('Resource group to deploy the solution') 4 | param resourceGroupName string 5 | 6 | @description('Location of the resource group') 7 | param location string 8 | 9 | @description('SQL server admin user name') 10 | param sqlServerAdmin string 11 | 12 | @description('Sql Server admin password') 13 | param sqlServerPassword string 14 | 15 | @description('Enable zone redundant for app service and SQL db') 16 | param enableZoneRedundant bool 17 | 18 | resource resourceGroup_resource 'Microsoft.Resources/resourceGroups@2021-04-01' = { 19 | name: resourceGroupName 20 | location: location 21 | } 22 | 23 | module monitoring 'nestedtemplates/monitoring.bicep' = { 24 | name: 'enableMonitor' 25 | scope: resourceGroup_resource 26 | params: { 27 | location: location 28 | } 29 | } 30 | 31 | module network 'nestedtemplates/networking.bicep' = { 32 | name: 'networkDeployment' 33 | scope: resourceGroup_resource 34 | params: { 35 | location: resourceGroup_resource.location 36 | lawsName: monitoring.outputs.lawsName_output 37 | } 38 | } 39 | 40 | module netwrokWatcher 'nestedtemplates/networkwatcher.bicep' = { 41 | name: 'netwrokWatcherFlowLowDeployment' 42 | scope: resourceGroup('NetworkWatcherRG') 43 | params: { 44 | location: location 45 | lawsId: monitoring.outputs.lawsId_output 46 | // nflStorageAccountId: network.outputs.nflStorageAccountId_output 47 | nsgId: network.outputs.nsgId_output 48 | nsgName: network.outputs.nsgName_output 49 | nflStorageAccountName: 'nflstgacc${uniqueString(resourceGroup_resource.id)}' 50 | } 51 | dependsOn: [ 52 | network 53 | monitoring 54 | ] 55 | } 56 | 57 | module keyvault 'nestedtemplates/keyvault.bicep' = { 58 | name: 'kvdeployment' 59 | scope: resourceGroup_resource 60 | params: { 61 | location: resourceGroup_resource.location 62 | endpointSubnetName: network.outputs.privateEnpointSubnetName_output 63 | virtualNetworkName: network.outputs.virtualNetworkName_output 64 | certsArray:[ 65 | { 66 | name: 'sslCert' 67 | value: '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' 68 | } 69 | ] 70 | lawsName: monitoring.outputs.lawsName_output 71 | } 72 | dependsOn: [ 73 | network 74 | monitoring 75 | ] 76 | } 77 | 78 | var keyVaultPrivateEndpointName_var = '${keyvault.outputs.keyVaultName_output}KeyVaultPrivateEndpoint' 79 | var keyVaultPublicDNSZoneForwarder_var = ((toLower(environment().name) == 'azureusgovernment') ? '.vaultcore.usgovcloudapi.net' : '.vaultcore.azure.net') 80 | var keyVaultPrivateDnsZoneName_var = 'privatelink${keyVaultPublicDNSZoneForwarder_var}' 81 | var keyVaultPrivateEndpointGroupName_var = 'vault' 82 | var keyVaultFQDN_var = '${keyvault.outputs.keyVaultName_output}${keyVaultPublicDNSZoneForwarder_var}' 83 | 84 | module ennableKVPrivateEndpoint 'nestedtemplates/privateEndpoints.bicep' = { 85 | name: 'kvPrivateEndpoint' 86 | scope: resourceGroup_resource 87 | params: { 88 | location: location 89 | virtualNetworkName: network.outputs.virtualNetworkName_output 90 | privateEndpointServiceId: keyvault.outputs.keyVaultId_output 91 | privateDNSZoneName: keyVaultPrivateDnsZoneName_var 92 | privateEndpointFQDN: keyVaultFQDN_var 93 | privateEndpointSubnetName: network.outputs.privateEnpointSubnetName_output 94 | groupType: keyVaultPrivateEndpointGroupName_var 95 | privateEndpointName: keyVaultPrivateEndpointName_var 96 | } 97 | dependsOn: [ 98 | keyvault 99 | network 100 | ] 101 | } 102 | 103 | module appService 'nestedtemplates/appservice.bicep' = { 104 | name: 'appDeployment' 105 | scope: resourceGroup_resource 106 | params: { 107 | keyvaultName: keyvault.outputs.keyVaultName_output 108 | virtualNetworkName: network.outputs.virtualNetworkName_output 109 | location: resourceGroup_resource.location 110 | appGWServiceEnpointSubnetName: network.outputs.applicationGWServiceEnpointSubnetName_output 111 | appServiceSubnetName: network.outputs.applicationSubnetName_output 112 | lawsName: monitoring.outputs.lawsName_output 113 | enableZoneRedundant: enableZoneRedundant 114 | } 115 | dependsOn: [ 116 | network 117 | keyvault 118 | monitoring 119 | ennableKVPrivateEndpoint 120 | ] 121 | } 122 | 123 | module appGateway 'nestedtemplates/applicationGW_https.bicep' = { 124 | name: 'applicationGWDeployment' 125 | scope: resourceGroup_resource 126 | params: { 127 | appGWServiceEnpointSubnetName: network.outputs.applicationGWServiceEnpointSubnetName_output 128 | applicationDNSName: 'myapp${uniqueString(resourceGroup_resource.id)}' 129 | appServiceName: appService.outputs.appServiceName_output 130 | virtualNetworkName: network.outputs.virtualNetworkName_output 131 | certName: 'sslCert' 132 | keyvaultName: keyvault.outputs.keyVaultName_output 133 | location: location 134 | lawsName: monitoring.outputs.lawsName_output 135 | } 136 | dependsOn: [ 137 | network 138 | appService 139 | keyvault 140 | monitoring 141 | ] 142 | } 143 | 144 | module database 'nestedtemplates/azuresql.bicep' = { 145 | name: 'databaseDeployment' 146 | scope: resourceGroup_resource 147 | params: { 148 | sqlServerAdmin: sqlServerAdmin 149 | sqlServerPassword: sqlServerPassword 150 | location: location 151 | enableZoneRedundant: enableZoneRedundant 152 | lawsName: monitoring.outputs.lawsName_output 153 | } 154 | dependsOn: [ 155 | monitoring 156 | ] 157 | } 158 | 159 | var dbPrivateEndpointName_var = '${database.outputs.sqlName_output}DBPrivateEndpoint' 160 | var dbPublicDNSZoneForwarder_var = environment().suffixes.sqlServerHostname 161 | var dbPrivateDnsZoneName_var = 'privatelink${dbPublicDNSZoneForwarder_var}' 162 | var dbPrivateEndpointGroupName_var = 'sqlServer' 163 | var dbFQDN_var = '${database.outputs.sqlName_output}${dbPublicDNSZoneForwarder_var}' 164 | 165 | module ennableDBPrivateEndpoint 'nestedtemplates/privateEndpoints.bicep' = { 166 | name: 'dbPrivateEndpoint' 167 | scope: resourceGroup_resource 168 | params: { 169 | location: location 170 | privateEndpointServiceId: database.outputs.sqlServerId_output 171 | groupType: dbPrivateEndpointGroupName_var 172 | privateEndpointName: dbPrivateEndpointName_var 173 | virtualNetworkName: network.outputs.virtualNetworkName_output 174 | privateEndpointSubnetName: network.outputs.privateEnpointSubnetName_output 175 | privateDNSZoneName: dbPrivateDnsZoneName_var 176 | privateEndpointFQDN: dbFQDN_var 177 | } 178 | dependsOn: [ 179 | database 180 | network 181 | ennableKVPrivateEndpoint 182 | ] 183 | } 184 | 185 | output lawsName_output string = monitoring.outputs.lawsName_output 186 | -------------------------------------------------------------------------------- /nestedtemplates/applicationGW_https.bicep: -------------------------------------------------------------------------------- 1 | @description('The location in which all resources should be deployed.') 2 | param location string = resourceGroup().location 3 | 4 | @description('Virtual network name') 5 | param virtualNetworkName string 6 | 7 | @description('application gateway name') 8 | param applicationGWName string = 'appGW${uniqueString(resourceGroup().id)}' 9 | 10 | @description('DNS name for the applicationtion frontend') 11 | param applicationDNSName string 12 | 13 | @description('Application GW serviceendpoint subnet name') 14 | param appGWServiceEnpointSubnetName string 15 | 16 | @description('app service name') 17 | param appServiceName string 18 | 19 | @description('Keyvault name') 20 | param keyvaultName string 21 | 22 | @description('SSL certificate') 23 | param certName string 24 | 25 | @description('application gateway autoscale min capacity') 26 | param applicationGatewayAutoScaleMinCapacity int = 2 27 | 28 | @description('application gateway autoscale max capacity') 29 | param applicationGatewayAutoScaleMaxCapacity int = 5 30 | 31 | @description('Specifies the Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. Get it by using Get-AzSubscription cmdlet.') 32 | param tenantId string = subscription().tenantId 33 | 34 | 35 | @description('Specifies the permissions to keys in the vault. Valid values are: all, encrypt, decrypt, wrapKey, unwrapKey, sign, verify, get, list, create, update, import, delete, backup, restore, recover, and purge.') 36 | param keysPermissions array = [ 37 | 'get' 38 | 'create' 39 | 'delete' 40 | 'list' 41 | 'update' 42 | 'import' 43 | 'backup' 44 | 'restore' 45 | 'recover' 46 | ] 47 | 48 | @description('Specifies the permissions to secrets in the vault. Valid values are: all, get, list, set, delete, backup, restore, recover, and purge.') 49 | param secretsPermissions array = [ 50 | 'get' 51 | 'list' 52 | 'set' 53 | 'delete' 54 | 'backup' 55 | 'restore' 56 | 'recover' 57 | ] 58 | 59 | @description('Specifies the permissions to certificates in the vault. Valid values are: all, get, list, set, delete, managecontacts, getissuers, listissuers, setissuers, deleteissuers, manageissuers, backup, and recover.') 60 | param certificatesPermissions array = [ 61 | 'get' 62 | 'list' 63 | 'delete' 64 | 'create' 65 | 'import' 66 | 'update' 67 | 'managecontacts' 68 | 'getissuers' 69 | 'listissuers' 70 | 'setissuers' 71 | 'deleteissuers' 72 | 'manageissuers' 73 | 'backup' 74 | 'recover' 75 | ] 76 | 77 | @description('Application gateway user assigned identity') 78 | param identityName string = 'appGWId${uniqueString(resourceGroup().id)}' 79 | 80 | @description('name of log analytics workspace') 81 | param lawsName string 82 | 83 | var applicationGWSkuName_var = 'WAF_v2' 84 | var applicationGWTierName_var = 'WAF_v2' 85 | var appGwIpConfigName_var = 'appGatewayIpConfigName' 86 | var appGwFrontendIpConfigName_var = 'appGatewayPublicFrontendIpConfig' 87 | var publicIpAddressName_var = 'myAppGatewayPublicIp-${uniqueString(resourceGroup().id)}' 88 | var publicIpAddressSku_var = 'Standard' 89 | var publicIpAddressAllocationType = 'Static' 90 | var webAppName_var = '${applicationDNSName}-${uniqueString(resourceGroup().id)}' 91 | var appGwFrontendPortName_var = 'appGatewayFrontendPort_80' 92 | var appGwFrontendPort_var = 443 93 | var appGwBackendAddressPoolName_var = 'appGateway${webAppName_var}BackendPool' 94 | var appGwHttpSettingName_var = 'appGatewayHttpSetting_443' 95 | var appGwListenerName_var = 'appGatewayListener' 96 | var appGwFrontendIpConfigId_var = resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations/', applicationGWName, appGwFrontendIpConfigName_var) 97 | var appGwFrontendPortId_var = resourceId('Microsoft.Network/applicationGateways/frontendPorts/', applicationGWName, appGwFrontendPortName_var) 98 | var appGwListenerId_var = resourceId('Microsoft.Network/applicationGateways/httpListeners/', applicationGWName, appGwListenerName_var) 99 | var appGwBackendAddressPoolId_var = resourceId('Microsoft.Network/applicationGateways/backendAddressPools/', applicationGWName, appGwBackendAddressPoolName_var) 100 | var appGwHttpSettingId_var = resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection/', applicationGWName, appGwHttpSettingName_var) 101 | var appGwRoutingRuleName_var = 'appGatewayRoutingRule' 102 | var appGwHttpSettingProbeName_var = 'appGatewayHttpSettingProbe_443' 103 | var applicationGWCert_var = '${webAppName_var}Cert' 104 | var appGWCertID_var = resourceId('Microsoft.Network/applicationGateways/sslCertificates', applicationGWName, applicationGWCert_var) 105 | 106 | resource virtualNetwork_resource 'Microsoft.Network/virtualNetworks@2021-03-01' existing = { 107 | name: virtualNetworkName 108 | } 109 | 110 | resource appGWServiceEnpointSubnet_resource 'Microsoft.Network/virtualNetworks/subnets@2021-03-01' existing = { 111 | name: appGWServiceEnpointSubnetName 112 | parent: virtualNetwork_resource 113 | } 114 | 115 | resource appService_resource 'Microsoft.Web/sites@2021-02-01' existing = { 116 | name: appServiceName 117 | } 118 | 119 | resource keyvault_resource 'Microsoft.KeyVault/vaults@2019-09-01' existing = { 120 | name: keyvaultName 121 | } 122 | 123 | resource publicIpAddress_resource 'Microsoft.Network/publicIPAddresses@2020-05-01' = { 124 | name: publicIpAddressName_var 125 | location: location 126 | sku: { 127 | name: publicIpAddressSku_var 128 | } 129 | properties: { 130 | publicIPAllocationMethod: publicIpAddressAllocationType 131 | dnsSettings: { 132 | domainNameLabel: toLower(webAppName_var) 133 | } 134 | } 135 | } 136 | 137 | var sslCertId = '${keyvault_resource.properties.vaultUri}secrets/${certName}' 138 | resource applicationGateway_resource 'Microsoft.Network/applicationGateways@2021-03-01' = { 139 | name: applicationGWName 140 | location: location 141 | identity: { 142 | type: 'UserAssigned' 143 | userAssignedIdentities: { 144 | '${appGWUserIdentity.id}' : {} 145 | } 146 | } 147 | properties: { 148 | sku: { 149 | name: applicationGWSkuName_var 150 | tier: applicationGWTierName_var 151 | } 152 | sslCertificates: [ 153 | { 154 | name: applicationGWCert_var 155 | properties: { 156 | keyVaultSecretId: sslCertId 157 | } 158 | } 159 | ] 160 | gatewayIPConfigurations: [ 161 | { 162 | name: appGwIpConfigName_var 163 | properties: { 164 | subnet: { 165 | id: appGWServiceEnpointSubnet_resource.id 166 | } 167 | } 168 | } 169 | ] 170 | frontendIPConfigurations: [ 171 | { 172 | name: appGwFrontendIpConfigName_var 173 | properties: { 174 | publicIPAddress: { 175 | id: publicIpAddress_resource.id 176 | } 177 | } 178 | } 179 | ] 180 | frontendPorts: [ 181 | { 182 | name: appGwFrontendPortName_var 183 | properties: { 184 | port: appGwFrontendPort_var 185 | } 186 | } 187 | ] 188 | backendAddressPools: [ 189 | { 190 | name: appGwBackendAddressPoolName_var 191 | properties: { 192 | backendAddresses: [ 193 | { 194 | fqdn: appService_resource.properties.hostNames[0] 195 | } 196 | ] 197 | } 198 | } 199 | ] 200 | backendHttpSettingsCollection: [ 201 | { 202 | name: appGwHttpSettingName_var 203 | properties: { 204 | port: 443 205 | protocol: 'Https' 206 | cookieBasedAffinity: 'Disabled' 207 | requestTimeout: 20 208 | pickHostNameFromBackendAddress: true 209 | } 210 | } 211 | ] 212 | httpListeners: [ 213 | { 214 | name: appGwListenerName_var 215 | properties: { 216 | frontendIPConfiguration: { 217 | id: appGwFrontendIpConfigId_var 218 | } 219 | frontendPort: { 220 | id: appGwFrontendPortId_var 221 | } 222 | protocol: 'Https' 223 | sslCertificate: { 224 | id: appGWCertID_var 225 | } 226 | } 227 | } 228 | ] 229 | requestRoutingRules: [ 230 | { 231 | name: appGwRoutingRuleName_var 232 | properties: { 233 | ruleType: 'Basic' 234 | httpListener: { 235 | id: appGwListenerId_var 236 | } 237 | backendAddressPool: { 238 | id: appGwBackendAddressPoolId_var 239 | } 240 | backendHttpSettings: { 241 | id: appGwHttpSettingId_var 242 | } 243 | } 244 | } 245 | ] 246 | enableHttp2: true 247 | probes: [ 248 | { 249 | name: appGwHttpSettingProbeName_var 250 | properties: { 251 | interval: 30 252 | minServers: 0 253 | path: '/' 254 | protocol: 'Https' 255 | timeout: 30 256 | unhealthyThreshold: 3 257 | pickHostNameFromBackendHttpSettings: true 258 | } 259 | } 260 | ] 261 | autoscaleConfiguration: { 262 | minCapacity: applicationGatewayAutoScaleMinCapacity 263 | maxCapacity: applicationGatewayAutoScaleMaxCapacity 264 | } 265 | webApplicationFirewallConfiguration: { 266 | enabled: true 267 | firewallMode: 'Prevention' 268 | ruleSetType: 'OWASP' 269 | ruleSetVersion: '3.2' 270 | } 271 | } 272 | dependsOn: [ 273 | appService_resource 274 | keyvaultAccessPolicies 275 | ] 276 | } 277 | 278 | resource appGWUserIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { 279 | name: identityName 280 | location: location 281 | } 282 | 283 | resource keyvaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2019-09-01' = { 284 | name: 'add' 285 | parent: keyvault_resource 286 | properties: { 287 | accessPolicies: [ 288 | { 289 | tenantId: tenantId 290 | objectId: reference(appGWUserIdentity.id).principalId 291 | permissions: { 292 | keys: keysPermissions 293 | certificates:certificatesPermissions 294 | secrets: secretsPermissions 295 | } 296 | } 297 | ] 298 | } 299 | } 300 | 301 | // setting up diagnostics logging 302 | resource laws_resource 'Microsoft.OperationalInsights/workspaces@2021-06-01' existing = { 303 | name: lawsName 304 | } 305 | 306 | resource appGWDiagnostics_resource 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 307 | name: '${applicationGWName}AllDiagnostic' 308 | scope: applicationGateway_resource 309 | properties: { 310 | workspaceId: laws_resource.id 311 | logs: [ 312 | { 313 | categoryGroup: 'allLogs' 314 | enabled: true 315 | } 316 | ] 317 | metrics: [ 318 | { 319 | category: 'AllMetrics' 320 | enabled: true 321 | } 322 | ] 323 | } 324 | } 325 | 326 | output appGWName_output string = applicationGateway_resource.name 327 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /nestedtemplates/appservice.bicep: -------------------------------------------------------------------------------- 1 | @description('The location in which all resources should be deployed.') 2 | param location string = resourceGroup().location 3 | 4 | @description('The name of the app to create.') 5 | param appName string = 'app${uniqueString(resourceGroup().id)}' 6 | 7 | @description('Name of veritual network where the private endpoint will be created') 8 | param virtualNetworkName string 9 | 10 | @description('Name of veritual network where the private endpoint will be created') 11 | param keyvaultName string 12 | 13 | @description('App Service SKU') 14 | param appServicePlanSku string = enableZoneRedundant ? 'P1v3' : 'S1' 15 | 16 | @description('Name of the default scaling rule') 17 | param autoscaleSettingName string = '${appName}DefaultScalingSetting' 18 | 19 | @description('Admistrators email to receive autoscale notification') 20 | param autoscaleNotificationEmail string = 'administrator@abc.com' 21 | 22 | 23 | @description('Specifies the permissions to keys in the vault. Valid values are: all, encrypt, decrypt, wrapKey, unwrapKey, sign, verify, get, list, create, update, import, delete, backup, restore, recover, and purge.') 24 | param keysPermissions array = [ 25 | 'get' 26 | 'create' 27 | 'delete' 28 | 'list' 29 | 'update' 30 | 'import' 31 | 'backup' 32 | 'restore' 33 | 'recover' 34 | ] 35 | 36 | @description('Specifies the permissions to secrets in the vault. Valid values are: all, get, list, set, delete, backup, restore, recover, and purge.') 37 | param secretsPermissions array = [ 38 | 'get' 39 | 'list' 40 | 'set' 41 | 'delete' 42 | 'backup' 43 | 'restore' 44 | 'recover' 45 | ] 46 | 47 | @description('Specifies the permissions to certificates in the vault. Valid values are: all, get, list, set, delete, managecontacts, getissuers, listissuers, setissuers, deleteissuers, manageissuers, backup, and recover.') 48 | param certificatesPermissions array = [ 49 | 'get' 50 | 'list' 51 | 'delete' 52 | 'create' 53 | 'import' 54 | 'update' 55 | 'managecontacts' 56 | 'getissuers' 57 | 'listissuers' 58 | 'setissuers' 59 | 'deleteissuers' 60 | 'manageissuers' 61 | 'backup' 62 | 'recover' 63 | ] 64 | 65 | @description('Specifies the Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. Get it by using Get-AzSubscription cmdlet.') 66 | param tenantId string = subscription().tenantId 67 | 68 | @description('Secete 1 name, for testing purpose') 69 | param secret1Name string = 'secret1' 70 | 71 | @description('Application subnet name') 72 | param appServiceSubnetName string 73 | 74 | @description('Application GW serviceendpoint subnet name') 75 | param appGWServiceEnpointSubnetName string 76 | 77 | @description('name of log analytics workspace') 78 | param lawsName string 79 | 80 | @description('Application Insights name') 81 | param applicationInsightsName string = '${appName}ApplicationInsight${uniqueString(resourceGroup().id)}' 82 | 83 | @description('App Service health check path') 84 | param appHealthCheckPath string = '/' 85 | 86 | @description('Enable remote debug. Only turn on this in dev / stagging env') 87 | param remoteDebugEnable bool = false 88 | 89 | @description('Enable snapshot debugger') 90 | param snapshotDebuggerEnale string = 'disabled' 91 | 92 | @description('Enable zone redudant') 93 | param enableZoneRedundant bool 94 | 95 | var appServicePlanName = '${appName}${uniqueString(subscription().subscriptionId)}' 96 | var keyVaultPublicDNSZoneForwarder = ((toLower(environment().name) == 'azureusgovernment') ? '.vaultcore.usgovcloudapi.net' : '.vaultcore.azure.net') 97 | var testSecret1Uri_var = 'https://${keyvaultName}${keyVaultPublicDNSZoneForwarder}/secrets/${secret1Name}/' 98 | 99 | resource virtualNetwork_resource 'Microsoft.Network/virtualNetworks@2021-03-01' existing = { 100 | name: virtualNetworkName 101 | } 102 | 103 | resource keyvault_resource 'Microsoft.KeyVault/vaults@2019-09-01' existing = { 104 | name: keyvaultName 105 | } 106 | 107 | resource endpointSubnet_resource 'Microsoft.Network/virtualNetworks/subnets@2021-03-01' existing = { 108 | name: appServiceSubnetName 109 | parent: virtualNetwork_resource 110 | } 111 | 112 | resource appGWServiceEnpointSubnet_resource 'Microsoft.Network/virtualNetworks/subnets@2021-03-01' existing = { 113 | name: appGWServiceEnpointSubnetName 114 | parent: virtualNetwork_resource 115 | } 116 | 117 | resource appServicePlan 'Microsoft.Web/serverfarms@2021-02-01' = { 118 | name: appServicePlanName 119 | location: location 120 | sku: { 121 | name: appServicePlanSku 122 | capacity: enableZoneRedundant? 3 : 1 123 | } 124 | kind: 'app' 125 | properties: { 126 | zoneRedundant: enableZoneRedundant 127 | } 128 | } 129 | 130 | // create the app service to host the application 131 | resource webApp_resource 'Microsoft.Web/sites@2021-01-01' = { 132 | name: appName 133 | location: location 134 | kind: 'app' 135 | properties: { 136 | serverFarmId: appServicePlan.id 137 | virtualNetworkSubnetId: endpointSubnet_resource.id 138 | httpsOnly: false 139 | siteConfig: { 140 | http20Enabled: true 141 | minTlsVersion: '1.2' 142 | ipSecurityRestrictions: [ 143 | { 144 | vnetSubnetResourceId: appGWServiceEnpointSubnet_resource.id 145 | action: 'Allow' 146 | tag: 'Default' 147 | priority: 200 148 | name: 'appGatewaySubnet' 149 | description: 'Isolate traffic to subnet containing Azure Application Gateway' 150 | } 151 | ] 152 | appSettings: [ 153 | { 154 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 155 | value: applicationInsights_resource.properties.InstrumentationKey 156 | } 157 | { 158 | name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' 159 | value: applicationInsights_resource.properties.ConnectionString 160 | } 161 | { 162 | name: 'ApplicationInsightsAgent_EXTENSION_VERSION' 163 | value: '~2' 164 | } 165 | { 166 | name: 'secret1' 167 | value: '@Microsoft.KeyVault(SecretUri=${testSecret1Uri_var})' 168 | } 169 | { 170 | name: 'XDT_MicrosoftApplicationInsights_Mode' 171 | value: 'recommended' 172 | } 173 | { 174 | name: 'APPINSIGHTS_PROFILERFEATURE_VERSION' 175 | value: '1.0.0' 176 | } 177 | { 178 | name: 'APPINSIGHTS_SNAPSHOTFEATURE_VERSION' 179 | value: '1.0.0' 180 | } 181 | { 182 | name: 'DiagnosticServices_EXTENSION_VERSION' 183 | value: '~3' 184 | } 185 | { 186 | name: 'SnapshotDebugger_EXTENSION_VERSION' 187 | value: snapshotDebuggerEnale // only enable this in dev env / stag env 188 | } 189 | ] 190 | healthCheckPath: appHealthCheckPath 191 | loadBalancing: 'LeastRequests' 192 | remoteDebuggingEnabled: remoteDebugEnable 193 | requestTracingEnabled: true 194 | vnetRouteAllEnabled: false 195 | } 196 | 197 | } 198 | identity: { 199 | type: 'SystemAssigned' 200 | } 201 | dependsOn: [ 202 | endpointSubnet_resource 203 | ] 204 | } 205 | 206 | // enable logging 207 | resource appServiceLogging 'Microsoft.Web/sites/config@2020-06-01' = { 208 | parent: webApp_resource 209 | name: 'logs' 210 | properties: { 211 | applicationLogs: { 212 | fileSystem: { 213 | level: 'Warning' 214 | } 215 | } 216 | httpLogs: { 217 | fileSystem: { 218 | retentionInMb: 40 219 | enabled: true 220 | } 221 | } 222 | failedRequestsTracing: { 223 | enabled: true 224 | } 225 | detailedErrorMessages: { 226 | enabled: true 227 | } 228 | } 229 | } 230 | 231 | /// enable app service to access keyvault via policy 232 | resource keyvaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2019-09-01' = { 233 | name: 'add' 234 | parent: keyvault_resource 235 | properties: { 236 | accessPolicies: [ 237 | { 238 | tenantId: tenantId 239 | objectId: webApp_resource.identity.principalId 240 | permissions: { 241 | keys: keysPermissions 242 | certificates:certificatesPermissions 243 | secrets: secretsPermissions 244 | } 245 | } 246 | ] 247 | } 248 | } 249 | 250 | // auto scaling 251 | resource appDefaultAutoScale_resource 'Microsoft.Insights/autoscalesettings@2021-05-01-preview' = { 252 | location: location 253 | name: autoscaleSettingName 254 | properties: { 255 | enabled: true 256 | name: autoscaleSettingName 257 | notifications: [ 258 | { 259 | operation: 'Scale' 260 | email: { 261 | customEmails: [ 262 | autoscaleNotificationEmail 263 | ] 264 | sendToSubscriptionAdministrator: true 265 | sendToSubscriptionCoAdministrators: false 266 | } 267 | webhooks: [] 268 | } 269 | ] 270 | predictiveAutoscalePolicy: { 271 | scaleMode: 'Disabled' 272 | } 273 | targetResourceLocation: location 274 | targetResourceUri: appServicePlan.id 275 | profiles: [ 276 | { 277 | name: '${autoscaleSettingName}Profile' 278 | capacity: { 279 | default: enableZoneRedundant ? '3' : '1' 280 | maximum: '6' 281 | minimum: enableZoneRedundant ? '3' : '1' 282 | } 283 | rules: [ 284 | { 285 | scaleAction: { 286 | direction: 'Increase' 287 | type: 'ChangeCount' 288 | value: '1' 289 | cooldown: 'PT10M' 290 | } 291 | metricTrigger: { 292 | metricName: 'CpuPercentage' 293 | metricNamespace: 'microsoft.web/serverfarms' 294 | metricResourceUri: appServicePlan.id 295 | operator: 'GreaterThan' 296 | statistic: 'Average' 297 | threshold: 80 298 | timeAggregation: 'Average' 299 | timeGrain: 'PT1M' 300 | timeWindow: 'PT10M' 301 | dimensions:[] 302 | dividePerInstance: false 303 | } 304 | } 305 | { 306 | scaleAction: { 307 | direction: 'Decrease' 308 | type: 'ChangeCount' 309 | value: '1' 310 | cooldown: 'PT10M' 311 | } 312 | metricTrigger: { 313 | metricName: 'CpuPercentage' 314 | metricNamespace: 'microsoft.web/serverfarms' 315 | metricResourceUri: appServicePlan.id 316 | operator: 'LessThan' 317 | statistic: 'Average' 318 | threshold: 50 319 | timeAggregation: 'Average' 320 | timeGrain: 'PT1M' 321 | timeWindow: 'PT10M' 322 | dimensions:[] 323 | dividePerInstance: false 324 | } 325 | } 326 | ] 327 | } 328 | ] 329 | } 330 | dependsOn: [ 331 | webApp_resource 332 | ] 333 | } 334 | 335 | // enable scale diagnostic logging setting 336 | resource scaleDiagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 337 | name: '${appName}AutoScaleAllDiagnostic' 338 | scope: appDefaultAutoScale_resource 339 | properties: { 340 | workspaceId: laws_resource.id 341 | logs: [ 342 | { 343 | categoryGroup: 'allLogs' 344 | enabled: true 345 | } 346 | ] 347 | metrics: [ 348 | { 349 | category: 'AllMetrics' 350 | enabled: true 351 | } 352 | ] 353 | } 354 | } 355 | 356 | // Create application insights component 357 | resource laws_resource 'Microsoft.OperationalInsights/workspaces@2021-06-01' existing = { 358 | name: lawsName 359 | } 360 | 361 | 362 | resource applicationInsights_resource 'Microsoft.Insights/components@2020-02-02-preview' = { 363 | name: applicationInsightsName 364 | location: location 365 | kind: 'web' 366 | properties: { 367 | Application_Type: 'web' 368 | WorkspaceResourceId: laws_resource.id 369 | } 370 | } 371 | 372 | 373 | output appServiceName_output string = appName 374 | 375 | // appendix for reference only 376 | // resource appServiceSiteExtension 'Microsoft.Web/sites/config@2021-02-01' = { 377 | // parent: webApp_resource 378 | // name: 'appsettings' 379 | // properties: { 380 | // secret1: '@Microsoft.KeyVault(SecretUri=${testSecret1Uri_var})' 381 | // } 382 | // } 383 | 384 | 385 | // resource appServiceSiteExtension 'Microsoft.Web/sites/config@2021-02-01' = { 386 | // parent: webApp_resource 387 | // name: 'appsettings' 388 | // properties: { 389 | // APPINSIGHTS_INSTRUMENTATIONKEY: applicationInsights_resource.properties.InstrumentationKey 390 | // APPINSIGHTS_PROFILERFEATURE_VERSION: '1.0.0' 391 | // APPINSIGHTS_SNAPSHOTFEATURE_VERSION: '1.0.0' 392 | // APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights_resource.properties.ConnectionString 393 | // ApplicationInsightsAgent_EXTENSION_VERSION: '~2' 394 | // DiagnosticServices_EXTENSION_VERSION: '~3' 395 | // InstrumentationEngine_EXTENSION_VERSION: 'disabled' 396 | // SnapshotDebugger_EXTENSION_VERSION: 'disabled' 397 | // XDT_MicrosoftApplicationInsights_Mode: 'recommended' 398 | // XDT_MicrosoftApplicationInsights_BaseExtensions: 'disabled' 399 | // XDT_MicrosoftApplicationInsights_Java: '1' 400 | // XDT_MicrosoftApplicationInsights_NodeJS: '1' 401 | // XDT_MicrosoftApplicationInsights_PreemptSdk: 'disabled' 402 | // secret1: '@Microsoft.KeyVault(SecretUri=${testSecret1Uri_var})' 403 | // } 404 | // } 405 | --------------------------------------------------------------------------------