Skip to content

Instantly share code, notes, and snippets.

@jlucaspains
Last active April 25, 2024 14:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jlucaspains/a39b5cc69185cf87d88087a49155fc02 to your computer and use it in GitHub Desktop.
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
@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
@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
@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
}
}
}
az deployment group create --resource-group 'rg-myapp-soutchcentralus-001' \
--template-file main.bicep \
--mode Incremental \
--parameters ./parameters.json \
--name webapp
@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
@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
]
}
{
"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"
}
}
@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