Using App Service Environment (ASE) without a wildcard SSL certificate

The Azure App Service Environment (ASE) is a private deployment of Azure App Service in a virtual network. It has recently become available in Azure Government and it offers many advantages for Government web applications. The ASE documentation calls for a wildcard SSL certificate, which may be problematic for some organizations. In this blog, I will discuss some alternatives to wildcard certificates for ASE.

The preferred configuration of the ASE for Government web applications would be the Internal Load Balancer configuration. To set this up, it is recommended that you use a wildcard SSL certificate with the Subject Alternative Names (SAN) covering *.your-domain-name and *.scm.your-domain-name. That certificate will be the default certificate for any new web app created, e.g. if you create an app called my-app.your-domain-name it would automatically have a certificate for the site itself (https://my-app.your-domain-name) and also for the Kudu console (https://my-app.scm.your-domain-name).

While that sounds pretty straightforward, there are a lot of Government agencies (and other large organizations) that are a bit allergic to wildcard certificates and it can be a bit of a heavy lift to change existing policies. But that doesn't mean that a Government organization cannot get started with ASE.

The workarounds that you can use are based on the fact that the certificate does not actually have to be a wildcard certificate, it just has to cover the sites you deploy and if your default ILB certificate doesn't cover a site you provision, you can manually add a cert to the sites that are not covered (and their Kudu consoles).

I have posted a template for deploying an ASE in Azure Commercial or Azure Government. In the same repository, I have a script for preparing an ASE deployment. This script will either generate a self-signed certificate (wildcard) for the ASE or you can supply one. You can simply get a certificate that covers the app names that your organization plans on using. As an example, the template deploys a Web App called ase-site and let's suppose you additionally planned on deploying the apps app1, app2, and app3. You now need a certificate covering 8 SANs.

It is a bit of a hassle to generate certificates for lots of SANs if you have to do some kind of HTTP validation with your Certificate Authority. I recommend using Let's Encrypt CA with DNS challenges. It can easily be automated for many SANs using certbot. I will walk through the commands to do that here for Ubuntu based Linux distributions, but the same works in Bash for Windows. First, if you don't have certbot installed, open a bash shell and do the following:

[shell]
$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install certbot
[/shell]

Now you can run a authentication/validation procedure for multiple domains:

[shell]
$ certbot certonly --manual --preferred-challenges dns \
--manual-auth-hook ./godaddy_dns_authenticator.sh \
-d app1.cloudynerd.us -d app1.scm.cloudynerd.us \
-d app2.cloudynerd.us -d app2.scm.cloudynerd.us \
-d app3.cloudynerd.us -d app3.scm.cloudynerd.us \
-d ase-site.cloudynerd.us -d ase-site.scm.cloudynerd.us
[/shell]

Notice the --manual-auth-hook ./godaddy_dns_authenticator.sh. What this argument does is to call the script godaddy_dns_authenticator.sh for every domain name. The script will be supplied with the CERTBOT_DOMAIN and CERTBOT_VALIDATION environment variables. In my case, I am using a domain registered with GoDaddy, which has an API that can be used to add the required TXT domain records. Most domain registrars have an API that can be used to make such changes. In the case of GoDaddy, the manual-auth-hook script can be as simple as:

[shell]
#!/bin/bash

godaddykey="<YOUR GODADDY KEY>"
godaddysecret="<YOUR GODADDY SECRET>"

# Strip only the top domain to get the zone id
DOMAIN=$(expr match "$CERTBOT_DOMAIN" '.*\.\(.*\..*\)')
SITE=$(expr match "$CERTBOT_DOMAIN" '\(.*\)\..*\..*')
CREATESITE="_acme-challenge.${SITE}"

OUT=$(curl -s -X PUT https://api.godaddy.com/v1/domains/${DOMAIN}/records/TXT/${CREATESITE} \
-H "Authorization: sso-key ${godaddykey}:${godaddysecret}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
--data '{"data": "'"$CERTBOT_VALIDATION"'", "ttl":3600}')

sleep 25
[/shell]

Certbot should guide you through the process but it is mostly automated. At the end of the process, you will need to export the certificate as a *.pfx file:

[shell]
$ openssl pkcs12 -inkey /etc/letsencrypt/live/app1.cloudynerd.us/privkey.pem \
-in /etc/letsencrypt/live/app1.cloudynerd.us/cert.pem \
-export -out /mnt/c/temp/cloudynerd-4apps.pfx
[/shell]

You can use the PrepareAseDeployment.ps1 from the repository mentioned above to prepare the ASE deployment with something like:

[ps]
.\scripts\PrepareAseDeployment.ps1 -CertificatePath 'C:\temp\cloudynerd-4apps.pfx' `
-DomainName 'cloudynerd.us' -OutFile 'C:\temp\ase-config.parameters.json'
[/ps]

And you can deploy with:

[ps]
New-AzureRmResourceGroup -Name RG-NAME -Location REGION-NAME

New-AzureRmResourceGroupDeployment -Name myasedeploy `
-TemplateUri https://raw.githubusercontent.com/hansenms/iac/master/ase/azuredeploy.json `
-TemplateParameterFile C:\temp\ase-config.parameters.json `
-ResourceGroupName RG-NAME
[/ps]

This will take a while to run (1.5-2 hours). Once the deployment is complete, you can deploy a virtual machine into the vnet where the ASE is deployed and connect to the Web App deployed in the ASE. In the example above, the URL would be https://ase-site.cloudynerd.us. Unless you have set up DNS for the virtual network, you will need to add the hostnames of any ASE Web Apps to the hosts file of the VM.

What you should see is that the ASE Web App has a valid certificate. You can also connect to the Kudu console https://ase-site.scm.cloudynerd.us, and it should have a valid certificate. If you create another Web App in the ASE with the name app1, app2, or app3, and it will also have a valid certificate since it is covered by the SANs we added to the certificate above.

This principle works as long as you have a predictable pattern for the applications within an organization. Given that these names are just the starting names of the Web Apps (equivalent to *.azurewebsites.net or *.azurewebsites.us for a regular Web App) and more custom domain names can be added, this approach may well work for an organization with well defined development projects and organization names. You should be able to add at least 100 SANs to a certificate.

Even with the best laid plans, it is, however, likely that somebody will need to create a web app with a name not covered by the default certificate. Keep in mind that slots will also need a certificate. If, for example, you create an app with the name app100 in the example above and you try to hit that site, you will notice that the certificate is not valid.

You then have two options. The first option is to generate a new default certificate (using the procedure above) and replace the ILB certificate in the ASE. This takes a while since it requires a reconfiguration of the ASE, so expect 20-30 minutes of time for the certificate to be available. If you are adding a range of new application names, this may be the right path. The second option is to just replace the certificate on the one application. Suppose you generate a valid cert for SANs app100.cloudynerd.us and app100.scm.cloudynerd.us (using the procedure above) and export it as c:\temp\app100.pfx, you can bind it to just a single app:

[ps]
New-AzureRmWebAppSSLBinding -ResourceGroupName app1 -WebAppName app100 `
-SslState SniEnabled -Name app100.cloudynerd.us `
-CertificateFilePath C:\temp\app100.pfx

New-AzureRmWebAppSSLBinding -ResourceGroupName app1 -WebAppName app100 `
-SslState SniEnabled -Name app100.scm.cloudynerd.us `
-CertificateFilePath C:\temp\app100.pfx
[/ps]

After this, you can hit the https://app100.cloudynerd.us site and the cert should be valid. Notice that you bind the certificate to the Kudu site as well. It also follows from the code above that you can of course use two different certificates if that is easier to manage. The downside of binding certificates to individual sites is that you will now need to managed multiple certificates with different expiration dates.

In conclusion, even if you are not able to obtain a wildcard certificate for your organization, it is still possible to operate an ASE with valid certificates. You can either have a default certificate covering all the combinations of application names that your organization would like to have or you can bind certificates to the individual sites. The two approaches can also be combined; a default certificate covering most sites and individual certificates for some sites. It is obvious easier to manage an ASE using a wildcard certificate, but for organizations where policy prevents such certificates, the described approach may well be a reasonable compromise.

Hopefully this can help organizations that need to use ASE to be in compliance but cannot get a wildcard certificate due to policy or technical constraints. Let me know if you have questions/comments/suggestion.