diff --git a/modules/.shared/.scripts/Set-CertificateInKeyVault.ps1 b/modules/.shared/.scripts/Set-CertificateInKeyVault.ps1 index 18028cc9ea..5f9bafaef5 100644 --- a/modules/.shared/.scripts/Set-CertificateInKeyVault.ps1 +++ b/modules/.shared/.scripts/Set-CertificateInKeyVault.ps1 @@ -11,17 +11,23 @@ Mandatory. The name of the Key Vault to add a new certificate to, or fetch the s .PARAMETER CertName Mandatory. The name of the certificate to generate or fetch the secret reference from +.PARAMETER CertSubjectName +Optional. The subject distinguished name is the name of the user of the certificate. The distinguished name for the certificate is a textual representation of the subject or issuer of the certificate. Default name is "CN=fabrikam.com" + .EXAMPLE -./Set-CertificateInKeyVault.ps1 -KeyVaultName 'myVault' -CertName 'myCert' +./Set-CertificateInKeyVault.ps1 -KeyVaultName 'myVault' -CertName 'myCert' -CertSubjectName 'CN=fabrikam.com' -Generate a new Key Vault Certificate or fetch its secret reference if already existing as 'myCert' in Key Vault 'myVault' +Generate a new Key Vault Certificate with the default or provided subject name, or fetch its secret reference if already existing as 'myCert' in Key Vault 'myVault' #> param( [Parameter(Mandatory = $true)] [string] $KeyVaultName, [Parameter(Mandatory = $true)] - [string] $CertName + [string] $CertName, + + [Parameter(Mandatory = $false)] + [string] $CertSubjectName = 'CN=fabrikam.com' ) $certificate = Get-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertName -ErrorAction 'SilentlyContinue' @@ -29,7 +35,7 @@ $certificate = Get-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertNam if (-not $certificate) { $policyInputObject = @{ SecretContentType = 'application/x-pkcs12' - SubjectName = 'CN=fabrikam.com' + SubjectName = $CertSubjectName IssuerName = 'Self' ValidityInMonths = 12 ReuseKeyOnRenewal = $true diff --git a/modules/Microsoft.Web/hostingEnvironments/.test/asev2/dependencies.bicep b/modules/Microsoft.Web/hostingEnvironments/.test/asev2/dependencies.bicep index 57e095a001..8ea40d41f7 100644 --- a/modules/Microsoft.Web/hostingEnvironments/.test/asev2/dependencies.bicep +++ b/modules/Microsoft.Web/hostingEnvironments/.test/asev2/dependencies.bicep @@ -13,25 +13,25 @@ param managedIdentityName string var addressPrefix = '10.0.0.0/16' resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2021-08-01' = { - name: networkSecurityGroupName - location: location - properties: { - securityRules: [ - { - name: 'AllowPortsForASE2' - properties: { - access: 'Allow' - destinationAddressPrefix: addressPrefix - destinationPortRange: '454-455' - direction: 'Inbound' - priority: 1020 - protocol: '*' - sourceAddressPrefix: 'AppServiceManagement' - sourcePortRange: '*' - } - } - ] - } + name: networkSecurityGroupName + location: location + properties: { + securityRules: [ + { + name: 'AllowPortsForASE2' + properties: { + access: 'Allow' + destinationAddressPrefix: addressPrefix + destinationPortRange: '454-455' + direction: 'Inbound' + priority: 1020 + protocol: '*' + sourceAddressPrefix: 'AppServiceManagement' + sourcePortRange: '*' + } + } + ] + } } resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { @@ -55,7 +55,7 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { { name: 'ase' properties: { - serviceName: 'Microsoft.Web/hostingEnvironments' + serviceName: 'Microsoft.Web/hostingEnvironments' } } ] @@ -76,3 +76,5 @@ output subnetResourceId string = virtualNetwork.properties.subnets[0].id @description('The principal ID of the created Managed Identity.') output managedIdentityPrincipalId string = managedIdentity.properties.principalId +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id diff --git a/modules/Microsoft.Web/hostingEnvironments/.test/asev2/deploy.test.bicep b/modules/Microsoft.Web/hostingEnvironments/.test/asev2/deploy.test.bicep index f430e57b75..77a3db1f24 100644 --- a/modules/Microsoft.Web/hostingEnvironments/.test/asev2/deploy.test.bicep +++ b/modules/Microsoft.Web/hostingEnvironments/.test/asev2/deploy.test.bicep @@ -62,6 +62,21 @@ module testDeployment '../../deploy.bicep' = { params: { enableDefaultTelemetry: enableDefaultTelemetry name: '<>${serviceShort}001' + location: resourceGroup.location + lock: 'CanNotDelete' + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalIds: [ + nestedDependencies.outputs.managedIdentityPrincipalId + ] + principalType: 'ServicePrincipal' + } + ] + tags: { + resourceType: 'App Service Environment' + hostingEnvironmentName: '<>${serviceShort}001' + } subnetResourceId: nestedDependencies.outputs.subnetResourceId clusterSettings: [ { @@ -74,17 +89,12 @@ module testDeployment '../../deploy.bicep' = { diagnosticWorkspaceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId diagnosticEventHubAuthorizationRuleId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId diagnosticEventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + systemAssignedIdentity: true + userAssignedIdentities: { + '${nestedDependencies.outputs.managedIdentityResourceId}': {} + } ipsslAddressCount: 2 kind: 'ASEv2' multiSize: 'Standard_D1_V2' - roleAssignments: [ - { - roleDefinitionIdOrName: 'Reader' - principalIds: [ - nestedDependencies.outputs.managedIdentityPrincipalId - ] - principalType: 'ServicePrincipal' - } - ] } } diff --git a/modules/Microsoft.Web/hostingEnvironments/.test/asev3/dependencies.bicep b/modules/Microsoft.Web/hostingEnvironments/.test/asev3/dependencies.bicep index 56572c8429..74137cddf1 100644 --- a/modules/Microsoft.Web/hostingEnvironments/.test/asev3/dependencies.bicep +++ b/modules/Microsoft.Web/hostingEnvironments/.test/asev3/dependencies.bicep @@ -10,28 +10,34 @@ param virtualNetworkName string @description('Required. The name of the Managed Identity to create.') param managedIdentityName string +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + +@description('Required. The name of the Deployment Script to create for the Certificate generation.') +param certDeploymentScriptName string + var addressPrefix = '10.0.0.0/16' resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2021-08-01' = { - name: networkSecurityGroupName - location: location - properties: { - securityRules: [ - { - name: 'AllowPortsForASE' - properties: { - access: 'Allow' - destinationAddressPrefix: '10.0.7.0/24' - destinationPortRange: '454-455' - direction: 'Inbound' - priority: 1010 - protocol: '*' - sourceAddressPrefix: 'AppServiceManagement' - sourcePortRange: '*' - } - } - ] - } + name: networkSecurityGroupName + location: location + properties: { + securityRules: [ + { + name: 'AllowPortsForASE' + properties: { + access: 'Allow' + destinationAddressPrefix: '10.0.7.0/24' + destinationPortRange: '454-455' + direction: 'Inbound' + priority: 1010 + protocol: '*' + sourceAddressPrefix: 'AppServiceManagement' + sourcePortRange: '*' + } + } + ] + } } resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { @@ -55,7 +61,7 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { { name: 'ase' properties: { - serviceName: 'Microsoft.Web/hostingEnvironments' + serviceName: 'Microsoft.Web/hostingEnvironments' } } ] @@ -70,9 +76,60 @@ resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018- location: location } +resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: null + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } +} + +resource keyPermissions 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('msi-${managedIdentity.name}-KeyVault-Admin-RoleAssignment') + scope: keyVault + properties: { + principalId: managedIdentity.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483') // Key Vault Administrator + principalType: 'ServicePrincipal' + } +} + +resource certDeploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: certDeploymentScriptName + location: location + kind: 'AzurePowerShell' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${managedIdentity.id}': {} + } + } + properties: { + azPowerShellVersion: '8.0' + retentionInterval: 'P1D' + arguments: '-KeyVaultName "${keyVault.name}" -CertName "asev3certificate" -CertSubjectName "CN=*.internal.contoso.com"' + scriptContent: loadTextContent('../../../../.shared/.scripts/Set-CertificateInKeyVault.ps1') + } +} + @description('The resource ID of the created Virtual Network Subnet.') output subnetResourceId string = virtualNetwork.properties.subnets[0].id @description('The principal ID of the created Managed Identity.') output managedIdentityPrincipalId string = managedIdentity.properties.principalId +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The URL of the created certificate.') +output certificateSecretUrl string = certDeploymentScript.properties.outputs.secretUrl diff --git a/modules/Microsoft.Web/hostingEnvironments/.test/asev3/deploy.test.bicep b/modules/Microsoft.Web/hostingEnvironments/.test/asev3/deploy.test.bicep index 9df7e8e8a6..ab40c87377 100644 --- a/modules/Microsoft.Web/hostingEnvironments/.test/asev3/deploy.test.bicep +++ b/modules/Microsoft.Web/hostingEnvironments/.test/asev3/deploy.test.bicep @@ -35,6 +35,8 @@ module nestedDependencies 'dependencies.bicep' = { networkSecurityGroupName: 'dep-<>-nsg-${serviceShort}' virtualNetworkName: 'dep-<>-vnet-${serviceShort}' managedIdentityName: 'dep-<>-msi-${serviceShort}' + keyVaultName: 'dep-<>-kv-${serviceShort}' + certDeploymentScriptName: 'dep-<>-ds-${serviceShort}' } } @@ -62,27 +64,45 @@ module testDeployment '../../deploy.bicep' = { params: { enableDefaultTelemetry: enableDefaultTelemetry name: '<>${serviceShort}001' + location: resourceGroup.location + lock: 'CanNotDelete' + roleAssignments: [ + { + roleDefinitionIdOrName: 'Reader' + principalIds: [ + nestedDependencies.outputs.managedIdentityPrincipalId + ] + principalType: 'ServicePrincipal' + } + ] + tags: { + resourceType: 'App Service Environment' + hostingEnvironmentName: '<>${serviceShort}001' + } subnetResourceId: nestedDependencies.outputs.subnetResourceId + internalLoadBalancingMode: 'Web, Publishing' clusterSettings: [ { name: 'DisableTls1.0' value: '1' } ] + allowNewPrivateEndpointConnections: true + ftpEnabled: true + inboundIpAddressOverride: '10.0.0.10' + remoteDebugEnabled: true + upgradePreference: 'Late' diagnosticLogsRetentionInDays: 7 diagnosticStorageAccountId: diagnosticDependencies.outputs.storageAccountResourceId diagnosticWorkspaceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId diagnosticEventHubAuthorizationRuleId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId diagnosticEventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName - lock: 'CanNotDelete' - roleAssignments: [ - { - roleDefinitionIdOrName: 'Reader' - principalIds: [ - nestedDependencies.outputs.managedIdentityPrincipalId - ] - principalType: 'ServicePrincipal' - } - ] + systemAssignedIdentity: true + userAssignedIdentities: { + '${nestedDependencies.outputs.managedIdentityResourceId}': {} + } + customDnsSuffix: 'internal.contoso.com' + customDnsSuffixCertificateUrl: nestedDependencies.outputs.certificateSecretUrl + customDnsSuffixKeyVaultReferenceIdentity: nestedDependencies.outputs.managedIdentityResourceId } } diff --git a/modules/Microsoft.Web/hostingEnvironments/configurations-customdnssuffix/deploy.bicep b/modules/Microsoft.Web/hostingEnvironments/configurations-customdnssuffix/deploy.bicep new file mode 100644 index 0000000000..118360ffe5 --- /dev/null +++ b/modules/Microsoft.Web/hostingEnvironments/configurations-customdnssuffix/deploy.bicep @@ -0,0 +1,49 @@ +@description('Conditional. The name of the parent Hosting Environment. Required if the template is used in a standalone deployment.') +param hostingEnvironmentName string + +@description('Required. Enable the default custom domain suffix to use for all sites deployed on the ASE.') +param dnsSuffix string + +@description('Required. The URL referencing the Azure Key Vault certificate secret that should be used as the default SSL/TLS certificate for sites with the custom domain suffix.') +param certificateUrl string + +@description('Required. The user-assigned identity to use for resolving the key vault certificate reference. If not specified, the system-assigned ASE identity will be used if available.') +param keyVaultReferenceIdentity string + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource appServiceEnvironment 'Microsoft.Web/hostingEnvironments@2022-03-01' existing = { + name: hostingEnvironmentName +} + +resource configuration 'Microsoft.Web/hostingEnvironments/configurations@2022-03-01' = { + name: 'customdnssuffix' + parent: appServiceEnvironment + properties: { + certificateUrl: certificateUrl + keyVaultReferenceIdentity: keyVaultReferenceIdentity + dnsSuffix: dnsSuffix + } +} + +@description('The name of the configuration.') +output name string = configuration.name + +@description('The resource ID of the deployed configuration.') +output resourceId string = configuration.id + +@description('The resource group of the deployed configuration.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/Microsoft.Web/hostingEnvironments/configurations-customdnssuffix/readme.md b/modules/Microsoft.Web/hostingEnvironments/configurations-customdnssuffix/readme.md new file mode 100644 index 0000000000..a38247f30c --- /dev/null +++ b/modules/Microsoft.Web/hostingEnvironments/configurations-customdnssuffix/readme.md @@ -0,0 +1,51 @@ +# Hosting Environment Custom DNS Suffix Configuration `[Microsoft.Web/hostingEnvironments/configurations-customdnssuffix]` + +This module deploys a Custom DNS Suffix Configuration for Hosting Environments. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Web/hostingEnvironments/configurations` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/hostingEnvironments/configurations) | + +## Parameters + +**Required parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `certificateUrl` | string | The URL referencing the Azure Key Vault certificate secret that should be used as the default SSL/TLS certificate for sites with the custom domain suffix. | +| `dnsSuffix` | string | Enable the default custom domain suffix to use for all sites deployed on the ASE. | +| `keyVaultReferenceIdentity` | string | The user-assigned identity to use for resolving the key vault certificate reference. If not specified, the system-assigned ASE identity will be used if available. | + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `hostingEnvironmentName` | string | The name of the parent Hosting Environment. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `enableDefaultTelemetry` | bool | `True` | Enable telemetry via a Globally Unique Identifier (GUID). | + + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the configuration. | +| `resourceGroupName` | string | The resource group of the deployed configuration. | +| `resourceId` | string | The resource ID of the deployed configuration. | + +## Cross-referenced modules + +_None_ diff --git a/modules/Microsoft.Web/hostingEnvironments/configurations-customdnssuffix/version.json b/modules/Microsoft.Web/hostingEnvironments/configurations-customdnssuffix/version.json new file mode 100644 index 0000000000..56f8d9ca40 --- /dev/null +++ b/modules/Microsoft.Web/hostingEnvironments/configurations-customdnssuffix/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.4" +} diff --git a/modules/Microsoft.Web/hostingEnvironments/configurations-networking/deploy.bicep b/modules/Microsoft.Web/hostingEnvironments/configurations-networking/deploy.bicep new file mode 100644 index 0000000000..320d11711e --- /dev/null +++ b/modules/Microsoft.Web/hostingEnvironments/configurations-networking/deploy.bicep @@ -0,0 +1,53 @@ +@description('Conditional. The name of the parent Hosting Environment. Required if the template is used in a standalone deployment.') +param hostingEnvironmentName string + +@description('Optional. Property to enable and disable new private endpoint connection creation on ASE.') +param allowNewPrivateEndpointConnections bool = false + +@description('Optional. Property to enable and disable FTP on ASEV3.') +param ftpEnabled bool = false + +@description('Optional. Customer provided Inbound IP Address. Only able to be set on Ase create.') +param inboundIpAddressOverride string = '' + +@description('Optional. Property to enable and disable Remote Debug on ASEv3.') +param remoteDebugEnabled bool = false + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { + name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + } + } +} + +resource appServiceEnvironment 'Microsoft.Web/hostingEnvironments@2022-03-01' existing = { + name: hostingEnvironmentName +} + +resource configuration 'Microsoft.Web/hostingEnvironments/configurations@2022-03-01' = { + name: 'networking' + parent: appServiceEnvironment + properties: { + allowNewPrivateEndpointConnections: allowNewPrivateEndpointConnections + ftpEnabled: ftpEnabled + inboundIpAddressOverride: inboundIpAddressOverride + remoteDebugEnabled: remoteDebugEnabled + } +} + +@description('The name of the configuration.') +output name string = configuration.name + +@description('The resource ID of the deployed configuration.') +output resourceId string = configuration.id + +@description('The resource group of the deployed configuration.') +output resourceGroupName string = resourceGroup().name diff --git a/modules/Microsoft.Web/hostingEnvironments/configurations-networking/readme.md b/modules/Microsoft.Web/hostingEnvironments/configurations-networking/readme.md new file mode 100644 index 0000000000..e395c507a8 --- /dev/null +++ b/modules/Microsoft.Web/hostingEnvironments/configurations-networking/readme.md @@ -0,0 +1,47 @@ +# Hosting Environment Network Configuration `[Microsoft.Web/hostingEnvironments/configurations-networking]` + +This module deploys a Network Configuration for Hosting Environments. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Web/hostingEnvironments/configurations` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/hostingEnvironments/configurations) | + +## Parameters + +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `hostingEnvironmentName` | string | The name of the parent Hosting Environment. Required if the template is used in a standalone deployment. | + +**Optional parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `allowNewPrivateEndpointConnections` | bool | `False` | Property to enable and disable new private endpoint connection creation on ASE. | +| `enableDefaultTelemetry` | bool | `True` | Enable telemetry via a Globally Unique Identifier (GUID). | +| `ftpEnabled` | bool | `False` | Property to enable and disable FTP on ASEV3. | +| `inboundIpAddressOverride` | string | `''` | Customer provided Inbound IP Address. Only able to be set on Ase create. | +| `remoteDebugEnabled` | bool | `False` | Property to enable and disable Remote Debug on ASEv3. | + + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the configuration. | +| `resourceGroupName` | string | The resource group of the deployed configuration. | +| `resourceId` | string | The resource ID of the deployed configuration. | + +## Cross-referenced modules + +_None_ diff --git a/modules/Microsoft.Web/hostingEnvironments/configurations-networking/version.json b/modules/Microsoft.Web/hostingEnvironments/configurations-networking/version.json new file mode 100644 index 0000000000..56f8d9ca40 --- /dev/null +++ b/modules/Microsoft.Web/hostingEnvironments/configurations-networking/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.4" +} diff --git a/modules/Microsoft.Web/hostingEnvironments/deploy.bicep b/modules/Microsoft.Web/hostingEnvironments/deploy.bicep index eb8c681914..61432c973c 100644 --- a/modules/Microsoft.Web/hostingEnvironments/deploy.bicep +++ b/modules/Microsoft.Web/hostingEnvironments/deploy.bicep @@ -5,21 +5,66 @@ param name string @description('Optional. Location for all resources.') param location string = resourceGroup().location +@allowed([ + '' + 'CanNotDelete' + 'ReadOnly' +]) +@description('Optional. Specify the type of lock.') +param lock string = '' + +@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') +param roleAssignments array = [] + +@description('Optional. Resource tags.') +param tags object = {} + +@allowed([ + 'ASEv2' + 'ASEv3' +]) @description('Optional. Kind of resource.') param kind string = 'ASEv3' -@description('Required. ResourceId for the subnet.') -param subnetResourceId string +@description('Optional. Custom settings for changing the behavior of the App Service Environment.') +param clusterSettings array = [ + { + name: 'DisableTls1.0' + value: '1' + } +] + +@description('Optional. Enable the default custom domain suffix to use for all sites deployed on the ASE. If provided, then customDnsSuffixCertificateUrl and customDnsSuffixKeyVaultReferenceIdentity are required. Cannot be used when kind is set to ASEv2.') +param customDnsSuffix string = '' + +@description('Conditional. The URL referencing the Azure Key Vault certificate secret that should be used as the default SSL/TLS certificate for sites with the custom domain suffix. Required if customDnsSuffix is not empty. Cannot be used when kind is set to ASEv2.') +param customDnsSuffixCertificateUrl string = '' + +@description('Conditional. The user-assigned identity to use for resolving the key vault certificate reference. If not specified, the system-assigned ASE identity will be used if available. Required if customDnsSuffix is not empty. Cannot be used when kind is set to ASEv2.') +param customDnsSuffixKeyVaultReferenceIdentity string = '' + +@description('Optional. The Dedicated Host Count. If `zoneRedundant` is false, and you want physical hardware isolation enabled, set to 2. Otherwise 0. Cannot be used when kind is set to ASEv2.') +param dedicatedHostCount int = 0 + +@description('Optional. DNS suffix of the App Service Environment.') +param dnsSuffix string = '' + +@description('Optional. Scale factor for frontends.') +param frontEndScaleFactor int = 15 -@description('Optional. Specifies which endpoints to serve internally in the Virtual Network for the App Service Environment. - None, Web, Publishing, Web,Publishing.') +@description('Optional. Specifies which endpoints to serve internally in the Virtual Network for the App Service Environment. - None, Web, Publishing, Web,Publishing. "None" Exposes the ASE-hosted apps on an internet-accessible IP address.') @allowed([ 'None' 'Web' 'Publishing' + 'Web, Publishing' ]) param internalLoadBalancingMode string = 'None' -@description('Optional. Frontend VM size. Cannot be used with \'kind\' `ASEv3`.') +@description('Optional. Number of IP SSL addresses reserved for the App Service Environment. Cannot be used when kind is set to ASEv3.') +param ipsslAddressCount int = 0 + +@description('Optional. Frontend VM size. Cannot be used when kind is set to ASEv3.') @allowed([ '' 'Medium' @@ -35,29 +80,42 @@ param internalLoadBalancingMode string = 'None' ]) param multiSize string = '' -@description('Optional. Number of IP SSL addresses reserved for the App Service Environment.') -param ipsslAddressCount int = -1 +@description('Optional. Property to enable and disable new private endpoint connection creation on ASE. Ignored when kind is set to ASEv2.') +param allowNewPrivateEndpointConnections bool = false -@description('Optional. DNS suffix of the App Service Environment.') -param dnsSuffix string = '' +@description('Optional. Property to enable and disable FTP on ASEV3. Ignored when kind is set to ASEv2.') +param ftpEnabled bool = false -@description('Optional. Scale factor for frontends.') -param frontEndScaleFactor int = 15 +@description('Optional. Customer provided Inbound IP Address. Only able to be set on Ase create. Ignored when kind is set to ASEv2.') +param inboundIpAddressOverride string = '' + +@description('Optional. Property to enable and disable Remote Debug on ASEv3. Ignored when kind is set to ASEv2.') +param remoteDebugEnabled bool = false + +@description('Optional. Specify preference for when and how the planned maintenance is applied.') +@allowed([ + 'Early' + 'Late' + 'Manual' + 'None' +]) +param upgradePreference string = 'None' + +@description('Required. ResourceId for the subnet.') +param subnetResourceId string @description('Optional. User added IP ranges to whitelist on ASE DB. Cannot be used with \'kind\' `ASEv3`.') param userWhitelistedIpRanges array = [] -@description('Optional. Custom settings for changing the behavior of the App Service Environment.') -param clusterSettings array = [ - { - name: 'DisableTls1.0' - value: '1' - } -] - @description('Optional. Switch to make the App Service Environment zone redundant. If enabled, the minimum App Service plan instance count will be three, otherwise 1. If enabled, the `dedicatedHostCount` must be set to `-1`.') param zoneRedundant bool = false +@description('Optional. Enables system assigned managed identity on the resource.') +param systemAssignedIdentity bool = false + +@description('Optional. The ID(s) to assign to the resource.') +param userAssignedIdentities object = {} + @description('Optional. Specifies the number of days that logs will be kept for; a value of 0 will retain data indefinitely.') @minValue(0) @maxValue(365) @@ -75,26 +133,9 @@ param diagnosticEventHubAuthorizationRuleId string = '' @description('Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category.') param diagnosticEventHubName string = '' -@allowed([ - '' - 'CanNotDelete' - 'ReadOnly' -]) -@description('Optional. Specify the type of lock.') -param lock string = '' - -@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') -param roleAssignments array = [] - -@description('Optional. Resource tags.') -param tags object = {} - @description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') param enableDefaultTelemetry bool = true -@description('Optional. The Dedicated Host Count. Is not supported by ASEv2. If `zoneRedundant` is false, and you want physical hardware isolation enabled, set to 2. Otherwise 0.') -param dedicatedHostCount int = -1 - @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource.') @allowed([ 'allLogs' @@ -127,6 +168,14 @@ var diagnosticsLogs = contains(diagnosticLogCategoriesToEnable, 'allLogs') ? [ } ] : diagnosticsLogsSpecified +var identityType = systemAssignedIdentity ? (!empty(userAssignedIdentities) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(userAssignedIdentities) ? 'UserAssigned' : 'None') +var enableReferencedModulesTelemetry = false + +var identity = identityType != 'None' ? { + type: identityType + userAssignedIdentities: !empty(userAssignedIdentities) ? userAssignedIdentities : null +} : {} + resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' properties: { @@ -139,28 +188,53 @@ resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (ena } } -resource appServiceEnvironment 'Microsoft.Web/hostingEnvironments@2021-03-01' = { +resource appServiceEnvironment 'Microsoft.Web/hostingEnvironments@2022-03-01' = { name: name kind: kind location: location tags: tags + identity: identity properties: { + clusterSettings: clusterSettings + dedicatedHostCount: dedicatedHostCount != 0 ? dedicatedHostCount : null + dnsSuffix: dnsSuffix + frontEndScaleFactor: frontEndScaleFactor + internalLoadBalancingMode: internalLoadBalancingMode + ipsslAddressCount: ipsslAddressCount != 0 ? ipsslAddressCount : null + multiSize: !empty(multiSize) ? any(multiSize) : null + upgradePreference: upgradePreference + userWhitelistedIpRanges: !empty(userWhitelistedIpRanges) ? userWhitelistedIpRanges : null virtualNetwork: { id: subnetResourceId subnet: last(split(subnetResourceId, '/')) } - internalLoadBalancingMode: internalLoadBalancingMode - multiSize: !empty(multiSize) ? any(multiSize) : null - ipsslAddressCount: ipsslAddressCount != -1 ? ipsslAddressCount : null - dnsSuffix: dnsSuffix - frontEndScaleFactor: frontEndScaleFactor - clusterSettings: clusterSettings - userWhitelistedIpRanges: !empty(userWhitelistedIpRanges) ? userWhitelistedIpRanges : null - dedicatedHostCount: dedicatedHostCount != -1 ? dedicatedHostCount : null zoneRedundant: zoneRedundant } } +module appServiceEnvironment_configurations_networking 'configurations-networking/deploy.bicep' = if (kind == 'ASEv3') { + name: '${uniqueString(deployment().name, location)}-AppServiceEnv-Configurations-Networking' + params: { + hostingEnvironmentName: appServiceEnvironment.name + allowNewPrivateEndpointConnections: allowNewPrivateEndpointConnections + ftpEnabled: ftpEnabled + inboundIpAddressOverride: inboundIpAddressOverride + remoteDebugEnabled: remoteDebugEnabled + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + +module appServiceEnvironment_configurations_customDnsSuffix 'configurations-customdnssuffix/deploy.bicep' = if (kind == 'ASEv3' && !empty(customDnsSuffix)) { + name: '${uniqueString(deployment().name, location)}-AppServiceEnv-Configurations-CustomDnsSuffix' + params: { + hostingEnvironmentName: appServiceEnvironment.name + certificateUrl: customDnsSuffixCertificateUrl + keyVaultReferenceIdentity: customDnsSuffixKeyVaultReferenceIdentity + dnsSuffix: customDnsSuffix + enableDefaultTelemetry: enableReferencedModulesTelemetry + } +} + resource appServiceEnvironment_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock)) { name: '${appServiceEnvironment.name}-${lock}-lock' properties: { @@ -195,13 +269,13 @@ module appServiceEnvironment_roleAssignments '.bicep/nested_roleAssignments.bice } }] -@description('The resource ID of the app service environment.') +@description('The resource ID of the App Service Environment.') output resourceId string = appServiceEnvironment.id -@description('The resource group the app service environment was deployed into.') +@description('The resource group the App Service Environment was deployed into.') output resourceGroupName string = resourceGroup().name -@description('The name of the app service environment.') +@description('The name of the App Service Environment.') output name string = appServiceEnvironment.name @description('The location the resource was deployed into.') diff --git a/modules/Microsoft.Web/hostingEnvironments/readme.md b/modules/Microsoft.Web/hostingEnvironments/readme.md index e23be060cc..d6338708bd 100644 --- a/modules/Microsoft.Web/hostingEnvironments/readme.md +++ b/modules/Microsoft.Web/hostingEnvironments/readme.md @@ -17,7 +17,8 @@ This module deploys an app service environment. | `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | -| `Microsoft.Web/hostingEnvironments` | [2021-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2021-03-01/hostingEnvironments) | +| `Microsoft.Web/hostingEnvironments` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-03-01/hostingEnvironments) | +| `Microsoft.Web/hostingEnvironments/configurations` | [2022-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/hostingEnvironments/configurations) | ## Parameters @@ -28,12 +29,21 @@ This module deploys an app service environment. | `name` | string | Name of the App Service Environment. | | `subnetResourceId` | string | ResourceId for the subnet. | +**Conditional parameters** + +| Parameter Name | Type | Default Value | Description | +| :-- | :-- | :-- | :-- | +| `customDnsSuffixCertificateUrl` | string | `''` | The URL referencing the Azure Key Vault certificate secret that should be used as the default SSL/TLS certificate for sites with the custom domain suffix. Required if customDnsSuffix is not empty. Cannot be used when kind is set to ASEv2. | +| `customDnsSuffixKeyVaultReferenceIdentity` | string | `''` | The user-assigned identity to use for resolving the key vault certificate reference. If not specified, the system-assigned ASE identity will be used if available. Required if customDnsSuffix is not empty. Cannot be used when kind is set to ASEv2. | + **Optional parameters** | Parameter Name | Type | Default Value | Allowed Values | Description | | :-- | :-- | :-- | :-- | :-- | +| `allowNewPrivateEndpointConnections` | bool | `False` | | Property to enable and disable new private endpoint connection creation on ASE. Ignored when kind is set to ASEv2. | | `clusterSettings` | array | `[System.Management.Automation.OrderedHashtable]` | | Custom settings for changing the behavior of the App Service Environment. | -| `dedicatedHostCount` | int | `-1` | | The Dedicated Host Count. Is not supported by ASEv2. If `zoneRedundant` is false, and you want physical hardware isolation enabled, set to 2. Otherwise 0. | +| `customDnsSuffix` | string | `''` | | Enable the default custom domain suffix to use for all sites deployed on the ASE. If provided, then customDnsSuffixCertificateUrl and customDnsSuffixKeyVaultReferenceIdentity are required. Cannot be used when kind is set to ASEv2. | +| `dedicatedHostCount` | int | `0` | | The Dedicated Host Count. If `zoneRedundant` is false, and you want physical hardware isolation enabled, set to 2. Otherwise 0. Cannot be used when kind is set to ASEv2. | | `diagnosticEventHubAuthorizationRuleId` | string | `''` | | Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. | | `diagnosticEventHubName` | string | `''` | | Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. | | `diagnosticLogCategoriesToEnable` | array | `[allLogs]` | `[allLogs, AppServiceEnvironmentPlatformLogs]` | The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. | @@ -44,14 +54,20 @@ This module deploys an app service environment. | `dnsSuffix` | string | `''` | | DNS suffix of the App Service Environment. | | `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | | `frontEndScaleFactor` | int | `15` | | Scale factor for frontends. | -| `internalLoadBalancingMode` | string | `'None'` | `[None, Publishing, Web]` | Specifies which endpoints to serve internally in the Virtual Network for the App Service Environment. - None, Web, Publishing, Web,Publishing. | -| `ipsslAddressCount` | int | `-1` | | Number of IP SSL addresses reserved for the App Service Environment. | -| `kind` | string | `'ASEv3'` | | Kind of resource. | +| `ftpEnabled` | bool | `False` | | Property to enable and disable FTP on ASEV3. Ignored when kind is set to ASEv2. | +| `inboundIpAddressOverride` | string | `''` | | Customer provided Inbound IP Address. Only able to be set on Ase create. Ignored when kind is set to ASEv2. | +| `internalLoadBalancingMode` | string | `'None'` | `[None, Publishing, Web, Web, Publishing]` | Specifies which endpoints to serve internally in the Virtual Network for the App Service Environment. - None, Web, Publishing, Web,Publishing. "None" Exposes the ASE-hosted apps on an internet-accessible IP address. | +| `ipsslAddressCount` | int | `0` | | Number of IP SSL addresses reserved for the App Service Environment. Cannot be used when kind is set to ASEv3. | +| `kind` | string | `'ASEv3'` | `[ASEv2, ASEv3]` | Kind of resource. | | `location` | string | `[resourceGroup().location]` | | Location for all resources. | | `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. | -| `multiSize` | string | `''` | `['', ExtraLarge, Large, Medium, Standard_D1_V2, Standard_D2, Standard_D2_V2, Standard_D3, Standard_D3_V2, Standard_D4, Standard_D4_V2]` | Frontend VM size. Cannot be used with 'kind' `ASEv3`. | +| `multiSize` | string | `''` | `['', ExtraLarge, Large, Medium, Standard_D1_V2, Standard_D2, Standard_D2_V2, Standard_D3, Standard_D3_V2, Standard_D4, Standard_D4_V2]` | Frontend VM size. Cannot be used when kind is set to ASEv3. | +| `remoteDebugEnabled` | bool | `False` | | Property to enable and disable Remote Debug on ASEv3. Ignored when kind is set to ASEv2. | | `roleAssignments` | array | `[]` | | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | +| `systemAssignedIdentity` | bool | `False` | | Enables system assigned managed identity on the resource. | | `tags` | object | `{object}` | | Resource tags. | +| `upgradePreference` | string | `'None'` | `[Early, Late, Manual, None]` | Specify preference for when and how the planned maintenance is applied. | +| `userAssignedIdentities` | object | `{object}` | | The ID(s) to assign to the resource. | | `userWhitelistedIpRanges` | array | `[]` | | User added IP ranges to whitelist on ASE DB. Cannot be used with 'kind' `ASEv3`. | | `zoneRedundant` | bool | `False` | | Switch to make the App Service Environment zone redundant. If enabled, the minimum App Service plan instance count will be three, otherwise 1. If enabled, the `dedicatedHostCount` must be set to `-1`. | @@ -191,14 +207,47 @@ tags: {

+### Parameter Usage: `userAssignedIdentities` + +You can specify multiple user assigned identities to a resource by providing additional resource IDs using the following format: + +

+ +Parameter JSON format + +```json +"userAssignedIdentities": { + "value": { + "/subscriptions/<>/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-001": {}, + "/subscriptions/<>/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-002": {} + } +} +``` + +
+ +
+ +Bicep format + +```bicep +userAssignedIdentities: { + '/subscriptions/<>/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-001': {} + '/subscriptions/<>/resourcegroups/validation-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/adp-sxx-az-msi-x-002': {} +} +``` + +
+

+ ## Outputs | Output Name | Type | Description | | :-- | :-- | :-- | | `location` | string | The location the resource was deployed into. | -| `name` | string | The name of the app service environment. | -| `resourceGroupName` | string | The resource group the app service environment was deployed into. | -| `resourceId` | string | The resource ID of the app service environment. | +| `name` | string | The name of the App Service Environment. | +| `resourceGroupName` | string | The resource group the App Service Environment was deployed into. | +| `resourceId` | string | The resource ID of the App Service Environment. | ## Cross-referenced modules @@ -239,6 +288,8 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = enableDefaultTelemetry: '' ipsslAddressCount: 2 kind: 'ASEv2' + location: '' + lock: 'CanNotDelete' multiSize: 'Standard_D1_V2' roleAssignments: [ { @@ -249,6 +300,14 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = roleDefinitionIdOrName: 'Reader' } ] + systemAssignedIdentity: true + tags: { + hostingEnvironmentName: '<>whasev2001' + resourceType: 'App Service Environment' + } + userAssignedIdentities: { + '': {} + } } } ``` @@ -305,6 +364,12 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = "kind": { "value": "ASEv2" }, + "location": { + "value": "" + }, + "lock": { + "value": "CanNotDelete" + }, "multiSize": { "value": "Standard_D1_V2" }, @@ -318,6 +383,20 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = "roleDefinitionIdOrName": "Reader" } ] + }, + "systemAssignedIdentity": { + "value": true + }, + "tags": { + "value": { + "hostingEnvironmentName": "<>whasev2001", + "resourceType": "App Service Environment" + } + }, + "userAssignedIdentities": { + "value": { + "": {} + } } } } @@ -340,19 +419,28 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = name: '<>whasev3001' subnetResourceId: '' // Non-required parameters + allowNewPrivateEndpointConnections: true clusterSettings: [ { name: 'DisableTls1.0' value: '1' } ] + customDnsSuffix: 'internal.contoso.com' + customDnsSuffixCertificateUrl: '' + customDnsSuffixKeyVaultReferenceIdentity: '' diagnosticEventHubAuthorizationRuleId: '' diagnosticEventHubName: '' diagnosticLogsRetentionInDays: 7 diagnosticStorageAccountId: '' diagnosticWorkspaceId: '' enableDefaultTelemetry: '' + ftpEnabled: true + inboundIpAddressOverride: '10.0.0.10' + internalLoadBalancingMode: 'Web Publishing' + location: '' lock: 'CanNotDelete' + remoteDebugEnabled: true roleAssignments: [ { principalIds: [ @@ -362,6 +450,15 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = roleDefinitionIdOrName: 'Reader' } ] + systemAssignedIdentity: true + tags: { + hostingEnvironmentName: '<>whasev3001' + resourceType: 'App Service Environment' + } + upgradePreference: 'Late' + userAssignedIdentities: { + '': {} + } } } ``` @@ -386,6 +483,9 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = "value": "" }, // Non-required parameters + "allowNewPrivateEndpointConnections": { + "value": true + }, "clusterSettings": { "value": [ { @@ -394,6 +494,15 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = } ] }, + "customDnsSuffix": { + "value": "internal.contoso.com" + }, + "customDnsSuffixCertificateUrl": { + "value": "" + }, + "customDnsSuffixKeyVaultReferenceIdentity": { + "value": "" + }, "diagnosticEventHubAuthorizationRuleId": { "value": "" }, @@ -412,9 +521,24 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = "enableDefaultTelemetry": { "value": "" }, + "ftpEnabled": { + "value": true + }, + "inboundIpAddressOverride": { + "value": "10.0.0.10" + }, + "internalLoadBalancingMode": { + "value": "Web, Publishing" + }, + "location": { + "value": "" + }, "lock": { "value": "CanNotDelete" }, + "remoteDebugEnabled": { + "value": true + }, "roleAssignments": { "value": [ { @@ -425,6 +549,23 @@ module hostingEnvironments './Microsoft.Web/hostingEnvironments/deploy.bicep' = "roleDefinitionIdOrName": "Reader" } ] + }, + "systemAssignedIdentity": { + "value": true + }, + "tags": { + "value": { + "hostingEnvironmentName": "<>whasev3001", + "resourceType": "App Service Environment" + } + }, + "upgradePreference": { + "value": "Late" + }, + "userAssignedIdentities": { + "value": { + "": {} + } } } }