How to deploy App Service with managed SSL certificate using ARM
Asked Answered
R

2

5

I want create an Azure App Service with a custom hostname binding and a managed SSL certificate.

When I create a single Bicep-template, the certificate resource can only be deployed if the hostname binding is already created. But to create a hostname binding, I need the certificate thumbprint.

Updating the hostname binding in the same template also is not possible, as a resource can only exist once in a template.

// hostname bindings must be deployed one by one to prevent Conflict (HTTP 429) errors.
@batchSize(1)
resource customHostnameWithoutSsl 'Microsoft.web/sites/hostnameBindings@2019-08-01' = [for fqdn in customHostnames: {
  name: '${webAppService.name}/${fqdn}'
  properties: {
    siteName: webAppService.name
    hostNameType: 'Verified'
    sslState: 'Disabled'
  }
}]

// Managed certificates can only be created once the hostname is added to the web app.
resource certificates 'Microsoft.Web/certificates@2022-03-01' = [for (fqdn, i) in customHostnames: {
  name: '${fqdn}-${webAppName}'
  location: location
  properties: {
    serverFarmId: appServicePlanResourceId
    canonicalName: fqdn
  }
  dependsOn: [ ]
}]

// sslState and thumbprint can only be set once the managed certificate is created
@batchSize(1)
resource customHostname 'Microsoft.web/sites/hostnameBindings@2019-08-01' = [for (fqdn, i) in customHostnames: {
  name: '${webAppService.name}/${fqdn}'
  properties: {
    siteName: webAppService.name
    hostNameType: 'Verified'
    sslState: 'SniEnabled'
    thumbprint: certificates[i].properties.thumbprint
  }
}]

Is there another way to create a single deployment template to deploy an Azure App Service with a managed SSL certificate for the custom hostname?

Ronnironnica answered 22/7, 2022 at 9:17 Comment(0)
R
10

Updating the hostname binding in the same template also is not possible, as a resource can only exist once in a template.

To prevent this error, the resource can be deployed using a Bicep module (or ARM nested template).

Then the solution becomes this:

webApp.bicep

@description('The name of the App Service Plan that this web app will be deployed to.')
param appServicePlanResourceId string

@description('The location that the resource will be deployed to')
param location string = resourceGroup().location

@description('The custom hostnames that you wish to add.')
param customHostnames array = []

@description('Deploy hostnames without SSL binding before creating the certificate. Required when hostname is not present yet.')
param redeployHostnames bool = false

resource webAppService 'Microsoft.Web/sites@2020-12-01' = {
  ...
}

// hostname bindings must be deployed one by one to prevent Conflict (HTTP 429) errors.
@batchSize(1)
resource customHostnameWithoutSsl 'Microsoft.web/sites/hostnameBindings@2019-08-01' = [for fqdn in customHostnames: if (redeployHostnames) {
  name: '${webAppService.name}/${fqdn}'
  properties: {
    siteName: webAppService.name
    hostNameType: 'Verified'
    sslState: 'Disabled'
  }
}]

// certificates must be bound via module/nested template, because each resource can only occur once in every template
// in this case the hostnameBindings would occur twice otherwise.
module certificateBindings './bindCertificateToHostname.bicep' = {
  name: '${deployment().name}-ssl'
  params: {
    appServicePlanResourceId: appServicePlanResourceId
    customHostnames: customHostnames
    location: location
    webAppName: webAppService.name
  }
  dependsOn: customHostnameWithoutSsl
}

bindCertificateToHostname.bicep

param webAppName string
param location string
param appServicePlanResourceId string
param customHostnames array

// Managed certificates can only be created once the hostname is added to the web app.
resource certificates 'Microsoft.Web/certificates@2022-03-01' = [for (fqdn, i) in customHostnames: {
  name: '${fqdn}-${webAppName}'
  location: location
  properties: {
    serverFarmId: appServicePlanResourceId
    canonicalName: fqdn
  }
}]

// sslState and thumbprint can only be set once the managed certificate is created
@batchSize(1)
resource customHostname 'Microsoft.web/sites/hostnameBindings@2019-08-01' = [for (fqdn, i) in customHostnames: {
  name: '${webAppName}/${fqdn}'
  properties: {
    siteName: webAppName
    hostNameType: 'Verified'
    sslState: 'SniEnabled'
    thumbprint: certificates[i].properties.thumbprint
  }
}]
Ronnironnica answered 22/7, 2022 at 13:46 Comment(0)
S
0

One of the workaround you can follow to achieve the above requirement ;

To deploy an app service with SSL certificate for the custom domain you can follow the complete configuration and template which is suggested by @bmoore-msft on this GitHub sample:-

Sample template.json:-

"resources": [
    {
        "type": "Microsoft.Web/serverfarms",
        "apiVersion": "2019-08-01",
        "name": "[variables('appServicePlanName')]",
        "location": "[parameters('location')]",
        "properties": {
            "name": "[variables('appServicePlanName')]"
        },
        "sku": {
            "name": "P1",
            "tier": "Premium",
            "size": "1",
            "family": "P",
            "capacity": "1"
        }
    },
    {
        "type": "Microsoft.Web/sites",
        "apiVersion": "2019-08-01",
        "name": "[parameters('webAppName')]",
        "location": "[parameters('location')]",
        "dependsOn": [
            "[resourceId('Microsoft.Web/serverFarms', variables('appServicePlanName'))]"
        ],
        "properties": {
            "name": "[parameters('webAppName')]",
            "serverFarmId": "[resourceId('Microsoft.Web/serverFarms', variables('appServicePlanName'))]"
        }
    },
    {
        "condition": "[variables('enableSSL')]",
        "type": "Microsoft.Web/certificates",
        "apiVersion": "2019-08-01",
        "name": "[variables('certificateName')]",
        "location": "[parameters('location')]",
        "dependsOn": [
            "[resourceId('Microsoft.Web/sites', parameters('webAppName'))]"
        ],
        "properties": {
            "keyVaultId": "[parameters('existingKeyVaultId')]",
            "keyVaultSecretName": "[parameters('existingKeyVaultSecretName')]",
            "serverFarmId": "[resourceId('Microsoft.Web/serverFarms', variables('appServicePlanName'))]"
        }
    },
    {
        "type": "Microsoft.Web/sites/hostnameBindings",
        "name": "[concat(parameters('webAppName'), '/', parameters('customHostname'))]",
        "apiVersion": "2019-08-01",
        "location": "[parameters('location')]",
        "dependsOn": [
            "[resourceId('Microsoft.Web/certificates', variables('certificateName'))]"
        ],
        "properties": {
            "sslState": "[if(variables('enableSSL'), 'SniEnabled', json('null'))]",
            "thumbprint": "[if(variables('enableSSL'), reference(resourceId('Microsoft.Web/certificates', variables('certificateName'))).Thumbprint, json('null'))]"
        }
    }

NOTE:- I am not able to test it with custom domain due to of some provision issue with our account

For more information please refer this SO THREAD| How to configure an App Service Managed Certificate

Selfsupporting answered 22/7, 2022 at 11:43 Comment(3)
The issue with this template is that it expects an existing SSL certificate to be present in the key vault. I'm trying to create an SSL certificate that is managed by the App Service, but that requires the hostname to be present (to proof that we are the owner, I assume). Providing the just the canonical name provisions a new SSL certificate, which is what I would like to do.Ronnironnica
Yes tt is.! This create with existing SSL binding. For create new it with ARM Template please refer this BlogSelfsupporting
I'm trying Alex's advice from the SO thread you linked now. Using a nested template (module in Bicep) seems to work.Ronnironnica

© 2022 - 2024 — McMap. All rights reserved.