Last active
April 25, 2024 14:40
-
-
Save jlucaspains/a39b5cc69185cf87d88087a49155fc02 to your computer and use it in GitHub Desktop.
Bicep to deploy application gateway and its required components for an externally visible Web App
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@description('App Service location. Default is the location of the resource group.') | |
param location string = resourceGroup().location | |
@description('App base name') | |
param appBaseName string = 'myapp' | |
@description('Environment Name.') | |
param envName string = 'dev' | |
var applicationGateWayName = 'apgw-${appBaseName}-${envName}-${location}-001' | |
resource applicationGateWayUser 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' = { | |
name: applicationGateWayName | |
location: location | |
} | |
output principalId string = applicationGateWayUser.properties.principalId | |
output resourceId string = applicationGateWayUser.id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@description('App Service location. Default is the location of the resource group.') | |
param location string = resourceGroup().location | |
@description('App base name') | |
param appBaseName string = 'myapp' | |
@description('Environment Name.') | |
param envName string = 'dev' | |
@description('Firewal mode') | |
@allowed([ 'Detection', 'Prevention' ]) | |
param firewallMode string = 'Detection' | |
var applicationGateWayName = 'apgw-${appBaseName}-${envName}-${location}-001' | |
resource applicationGateWayPolicy 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2022-11-01' = { | |
name: applicationGateWayName | |
location: location | |
properties: { | |
policySettings: { | |
requestBodyCheck: true | |
maxRequestBodySizeInKb: 128 | |
fileUploadLimitInMb: 100 | |
state: 'Enabled' | |
mode: firewallMode | |
} | |
managedRules: { | |
managedRuleSets: [ | |
{ | |
ruleSetType: 'OWASP' | |
ruleSetVersion: '3.2' | |
ruleGroupOverrides: [ | |
{ | |
ruleGroupName: 'REQUEST-920-PROTOCOL-ENFORCEMENT' | |
rules: [ | |
{ | |
ruleId: '920300' // Request missing an Accept header | |
state: 'Enabled' | |
action: 'Log' | |
} | |
] | |
} | |
] | |
} | |
] | |
exclusions: [ | |
{ | |
exclusionManagedRuleSets: [ | |
{ | |
ruleSetType: 'OWASP' | |
ruleSetVersion: '3.2' | |
ruleGroups: [ | |
{ | |
ruleGroupName: 'REQUEST-942-APPLICATION-ATTACK-SQLI' | |
rules: [ | |
{ | |
ruleId: '942450' // SQL Hex Encoding Identified; Happens with base64 encoded text | |
} | |
] | |
} | |
] | |
} | |
] | |
matchVariable: 'RequestCookieNames' | |
selector: 'myapp_session' | |
selectorMatchOperator: 'Equals' | |
} | |
] | |
} | |
} | |
} | |
output id string = applicationGateWayPolicy.id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@description('App Service location. Default is the location of the resource group.') | |
param location string = resourceGroup().location | |
@description('App base name') | |
param appBaseName string = 'myapp' | |
@description('Environment Name.') | |
param envName string = 'dev' | |
@description('Default FQDN to be used to access MyApp') | |
param site_FQDN string | |
@description('User assigned identity for App Gateway') | |
param identityId string | |
@description('Key Vault Id to retrieve SSL Certs from') | |
param keyVaultId string | |
@description('Key Vault to retrieve SSL Certs from') | |
param keyVaultCertName string | |
@description('Custom host name to be used by the web app.') | |
param webAppCustomHostName string = '' | |
@description('Error pages base Url. Pages should be named 502.html and 403.html.') | |
param errorBaseUrl string = '' | |
@description('Firewal mode') | |
@allowed([ 'Detection', 'Prevention' ]) | |
param firewallMode string = 'Detection' | |
var virtualNetworkName = 'vnet-${appBaseName}-${envName}-${location}-001' | |
var publicIPAddressName = 'pip-${appBaseName}-${envName}-${location}-0010' | |
var applicationGateWayName = 'apgw-${appBaseName}-${envName}-${location}-001' | |
var subnet_name = 'App_Gateway_${envName}' | |
var management_resourcegroup = resourceGroup().name | |
// Create VNet for the app gateway | |
resource vnet 'Microsoft.Network/virtualNetworks@2022-11-01' = { | |
name: virtualNetworkName | |
location: location | |
properties: { | |
addressSpace: { | |
addressPrefixes: [ | |
'10.0.0.0/16' | |
] | |
} | |
subnets: [ | |
{ | |
name: subnet_name | |
properties: { | |
addressPrefix: '10.0.0.0/24' | |
} | |
} | |
] | |
} | |
} | |
resource publicIPAddress 'Microsoft.Network/publicIPAddresses@2021-05-01' = { | |
name: publicIPAddressName | |
location: location | |
sku: { | |
name: 'Standard' | |
} | |
properties: { | |
publicIPAddressVersion: 'IPv4' | |
publicIPAllocationMethod: 'Static' | |
idleTimeoutInMinutes: 4 | |
} | |
} | |
module wafPolicy './app-gateway-policy.bicep' = { | |
name: 'wafPolicy' | |
params: { | |
appBaseName: appBaseName | |
envName: envName | |
location: location | |
firewallMode: firewallMode | |
} | |
} | |
var splitId = split(keyVaultId, '/') | |
resource applicationGateWay 'Microsoft.Network/applicationGateways@2021-05-01' = { | |
name: applicationGateWayName | |
location: location | |
identity: { | |
type: 'UserAssigned' | |
userAssignedIdentities: { | |
'${identityId}': {} | |
} | |
} | |
dependsOn: [ | |
vnet | |
publicIPAddress | |
wafPolicy | |
] | |
properties: { | |
sku: { | |
name: 'WAF_v2' | |
tier: 'WAF_v2' | |
} | |
firewallPolicy: { | |
id: wafPolicy.outputs.id | |
} | |
sslCertificates: [ | |
{ | |
name: 'ssl-appgw-external' // import the cert from a key vault | |
properties: { | |
keyVaultSecretId: 'https://${splitId[8]}${environment().suffixes.keyvaultDns}/secrets/${keyVaultCertName}' | |
} | |
} | |
] | |
gatewayIPConfigurations: [ | |
{ | |
name: 'appGatewayIpConfig-${envName}' | |
properties: { | |
subnet: { | |
id: resourceId(management_resourcegroup, 'Microsoft.Network/virtualNetworks/subnets', virtualNetworkName, subnet_name) | |
} | |
} | |
} | |
] | |
frontendIPConfigurations: [ | |
{ | |
name: 'appGwPublicFrontendIp-${envName}' | |
properties: { | |
privateIPAllocationMethod: 'Dynamic' | |
publicIPAddress: { | |
id: resourceId('Microsoft.Network/publicIPAddresses', publicIPAddressName) | |
} | |
} | |
} | |
] | |
frontendPorts: [ | |
{ | |
name: 'port_443' | |
properties: { | |
port: 443 | |
} | |
} | |
{ | |
name: 'port_80' | |
properties: { | |
port: 80 | |
} | |
} | |
] | |
backendAddressPools: [ | |
{ | |
name: 'MyApp-BackendPool-${envName}' | |
properties: { | |
backendAddresses: [ | |
{ | |
fqdn: site_FQDN | |
} | |
] | |
} | |
} | |
] | |
backendHttpSettingsCollection: [ | |
{ | |
name: 'BackendHttpSettings-${envName}' | |
properties: { | |
port: 443 | |
protocol: 'Https' | |
cookieBasedAffinity: 'Disabled' | |
pickHostNameFromBackendAddress: true | |
requestTimeout: 600 | |
probe: { | |
id: resourceId('Microsoft.Network/applicationGateways/probes', applicationGateWayName, 'HttpsHealthProbe') | |
} | |
} | |
} | |
] | |
httpListeners: [ | |
{ | |
name: 'MyApp-${envName}-listener-port443' | |
properties: { | |
frontendIPConfiguration: { | |
id: resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', applicationGateWayName, 'appGwPublicFrontendIp-${envName}') | |
} | |
frontendPort: { | |
id: resourceId('Microsoft.Network/applicationGateways/frontendPorts', applicationGateWayName, 'port_443') | |
} | |
protocol: 'Https' | |
requireServerNameIndication: false | |
sslCertificate: { | |
id: resourceId('Microsoft.Network/applicationGateways/sslCertificates', applicationGateWayName, 'ssl-appgw-external') | |
} | |
hostName: webAppCustomHostName | |
customErrorConfigurations: !empty(errorBaseUrl) ? [ | |
{ | |
statusCode: 'HttpStatus502' | |
customErrorPageUrl: '${errorBaseUrl}/502.html' | |
} | |
{ | |
statusCode: 'HttpStatus403' | |
customErrorPageUrl: '${errorBaseUrl}/403.html' | |
} | |
] : [] | |
} | |
} | |
{ | |
name: 'MyApp-${envName}-listener-port80' | |
properties: { | |
frontendIPConfiguration: { | |
id: resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', applicationGateWayName, 'appGwPublicFrontendIp-${envName}') | |
} | |
frontendPort: { | |
id: resourceId('Microsoft.Network/applicationGateways/frontendPorts', applicationGateWayName, 'port_80') | |
} | |
protocol: 'Http' | |
requireServerNameIndication: false | |
} | |
} | |
] | |
requestRoutingRules: [ | |
{ | |
name: 'myRoutingRuleHttps' | |
properties: { | |
ruleType: 'Basic' | |
priority: 10 | |
httpListener: { | |
id: resourceId('Microsoft.Network/applicationGateways/httpListeners', applicationGateWayName, 'MyApp-${envName}-listener-port443') | |
} | |
backendAddressPool: { | |
id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', applicationGateWayName, 'MyApp-BackendPool-${envName}') | |
} | |
backendHttpSettings: { | |
id: resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', applicationGateWayName, 'BackendHttpSettings-${envName}') | |
} | |
} | |
} | |
{ | |
name: 'myRoutingRuleHttp' | |
properties: { | |
ruleType: 'Basic' | |
priority: 20 | |
redirectConfiguration: { | |
id: resourceId('Microsoft.Network/applicationGateways/redirectConfigurations', applicationGateWayName, 'redirectHttpToHttps') | |
} | |
httpListener: { | |
id: resourceId('Microsoft.Network/applicationGateways/httpListeners', applicationGateWayName, 'MyApp-${envName}-listener-port80') | |
} | |
} | |
} | |
] | |
redirectConfigurations: [ | |
{ | |
name: 'redirectHttpToHttps' | |
properties: { | |
redirectType: 'Permanent' | |
includePath: true | |
includeQueryString: true | |
targetListener: { | |
id: resourceId('Microsoft.Network/applicationGateways/httpListeners', applicationGateWayName, 'MyApp-${envName}-listener-port443') | |
} | |
requestRoutingRules: [ | |
{ | |
id: resourceId('Microsoft.Network/applicationGateways/requestRoutingRules', applicationGateWayName, 'myRoutingRuleHttp') | |
} | |
] | |
} | |
} | |
] | |
probes: [ | |
{ | |
name: 'HttpsHealthProbe' | |
properties: { | |
protocol: 'Https' | |
host: site_FQDN | |
interval: 30 | |
timeout: 30 | |
unhealthyThreshold: 3 | |
pickHostNameFromBackendHttpSettings: false | |
path: '/health' | |
} | |
} | |
] | |
enableHttp2: true | |
autoscaleConfiguration: { | |
minCapacity: 0 | |
maxCapacity: 10 | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
az deployment group create --resource-group 'rg-myapp-soutchcentralus-001' \ | |
--template-file main.bicep \ | |
--mode Incremental \ | |
--parameters ./parameters.json \ | |
--name webapp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@description('App Service location. Default is the location of the resource group.') | |
param location string = resourceGroup().location | |
@description('App base name') | |
param appBaseName string = 'myapp' | |
@description('Environment Name.') | |
param envName string = 'dev' | |
@description('Web Site name to grant access to.') | |
param webSiteName string | |
@description('App gateway user assigned identity for certs.') | |
param appGatewayIdentity string | |
var name = toLower('kv-${appBaseName}-${envName}-001') | |
var skuName = 'standard' | |
var skuFamily = 'A' | |
resource appService 'Microsoft.Web/sites@2022-03-01' existing = { | |
name: webSiteName | |
} | |
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { | |
location: location | |
name: name | |
tags: { | |
project: appBaseName | |
environment: envName | |
} | |
properties: { | |
sku: { | |
name: skuName | |
family: skuFamily | |
} | |
tenantId: subscription().tenantId | |
accessPolicies: [ | |
{ | |
objectId: appService.identity.principalId | |
permissions: { | |
secrets: [ | |
'get', 'list' | |
] | |
} | |
tenantId: subscription().tenantId | |
} | |
{ | |
objectId: appGatewayIdentity | |
permissions: { | |
secrets: [ | |
'get' | |
] | |
certificates: [ | |
'get' | |
] | |
} | |
tenantId: subscription().tenantId | |
} | |
] | |
enabledForDeployment: false | |
enabledForDiskEncryption: false | |
enabledForTemplateDeployment: true | |
enableSoftDelete: true | |
softDeleteRetentionInDays: 90 | |
enableRbacAuthorization: false | |
createMode: 'default' | |
enablePurgeProtection: true | |
networkAcls: { | |
bypass: 'AzureServices' | |
defaultAction: 'Deny' | |
} | |
} | |
} | |
output name string = keyVault.name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@description('Specifies the location for resources.') | |
param location string = resourceGroup().location | |
@description('Environment Name.') | |
param envName string = 'dev' | |
@description('Key vault name containing the ssl certificate.') | |
param sslCertificateKeyVaultId string = '' | |
@description('SSL certificate name within the key vault.') | |
param sslCertificateName string = '' | |
@description('App Gateway Base url for error pages. Page names must be 403.html and 502.html.') | |
param apgwErrorBaseUrl string = '' | |
@description('App Gateway firewal mode') | |
@allowed([ 'Detection', 'Prevention' ]) | |
param apgwFirewallMode string = 'Detection' | |
@description('Custom host name to be used by the web app.') | |
param webAppCustomHostName string = '' | |
@description('Web App Service SKU. Default is B1.') | |
param webAppSKU string = 'B1' | |
var appBaseName = 'myapp' | |
module webApp './web-app.bicep' = { | |
name: 'WebDeployment' | |
params: { | |
location: location | |
appBaseName: appBaseName | |
envName: envName | |
sku: webAppSKU | |
} | |
} | |
module keyVault './key-vault.bicep' = { | |
name: 'KeyVaultDeployment' | |
dependsOn: [ | |
webApp | |
] | |
params: { | |
location: location | |
appBaseName: appBaseName | |
envName: envName | |
webSiteName: webApp.outputs.name | |
appGatewayIdentity: appGatewayAssignedIdentity.outputs.principalId | |
} | |
} | |
module appGatewayAssignedIdentity './app-gateway-identity.bicep' = { | |
name: 'AppGatewayAssignedIdentityDeployment' | |
params: { | |
location: location | |
appBaseName: appBaseName | |
envName: envName | |
} | |
} | |
module appGateway './app-gateway.bicep' = { | |
name: 'AppGatewayDeployment' | |
params: { | |
site_FQDN: webApp.outputs.website_fqdn | |
appBaseName: appBaseName | |
envName: envName | |
location: location | |
identityId: appGatewayAssignedIdentity.outputs.resourceId | |
keyVaultId: sslCertificateKeyVaultId | |
keyVaultCertName: sslCertificateName | |
webAppCustomHostName: webAppCustomHostName | |
errorBaseUrl: apgwErrorBaseUrl | |
firewallMode: apgwFirewallMode | |
} | |
dependsOn: [ | |
keyVault | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"sslCertificateKeyVaultId": { | |
"value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-myapp-soutchcentralus-001/providers/Microsoft.KeyVault/vaults/kv-myapp-dev-001" | |
}, | |
"sslCertificateName": { | |
"value": "myapp-mydomain-com" | |
}, | |
"apgwErrorBaseUrl": { | |
"value": "" | |
}, | |
"apgwFirewallMode": { | |
"value": "Prevention" | |
}, | |
"webAppCustomHostName": { | |
"value": "myapp.mydomain.com" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@description('App Service location. Default is the location of the resource group.') | |
param location string = resourceGroup().location | |
@description('App base name') | |
param appBaseName string = 'myapp' | |
@description('Environment Name.') | |
param envName string = 'dev' | |
@description('App Service SKU. Default is B1.') | |
param sku string = 'B1' | |
var webSiteName = toLower('app-${appBaseName}-${envName}-${location}-001') | |
var appServiceName = toLower('asp-${appBaseName}-${envName}-${location}-001') | |
var linuxFxVersion = 'DOTNETCORE|7.0' | |
resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { | |
name: appServiceName | |
location: location | |
tags: { | |
project: appBaseName | |
environment: envName | |
} | |
properties: { | |
reserved: true | |
} | |
sku: { | |
name: sku | |
} | |
kind: 'linux' | |
} | |
resource appService 'Microsoft.Web/sites@2022-03-01' = { | |
location: location | |
name: webSiteName | |
tags: { | |
project: appBaseName | |
environment: envName | |
} | |
identity: { | |
type: 'SystemAssigned' | |
} | |
properties: { | |
serverFarmId: appServicePlan.id | |
httpsOnly: true | |
clientAffinityEnabled: false | |
siteConfig: { | |
linuxFxVersion: linuxFxVersion | |
ftpsState: 'Disabled' | |
minTlsVersion: '1.2' | |
http20Enabled: true | |
healthCheckPath: '/health' | |
ipSecurityRestrictions: [ | |
{ | |
action: 'Allow' | |
description: 'Allow Azure services access' | |
ipAddress: 'AzureCloud' | |
name: 'AzureCloud_access' | |
priority: 50 | |
tag: 'ServiceTag' | |
} | |
] | |
scmIpSecurityRestrictionsUseMain: true | |
} | |
} | |
} | |
output name string = appService.name | |
output website_fqdn string = appService.properties.defaultHostName |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment