Did you know that you can get SSL certificates for free and let them be renewed automatically?
Check out Let's Encrypt for more information. And that is not just for dev/test purposes. A lot of public websites are using those certificates.
Of course, as for any free stuff, there are some limitations. For example how many certificates you can generate. Pay attention that Let's Encrypt certificates are valid for 90 days. However, that is absolutely not a problem if you have an automatic renewal in place.
If you are using Azure App Service without Application Gateway in front, there are some Extensions for Let's Encrypt that can be used for certificate renewal. Read more about that in this great article from awesome Scott Hanselmann.
In this post, I would like to show how to do it in Application Gateway.
As you may know in Azure Application Gateway you'll have HTTPS listener(s) that need to have an SSL certificate assigned. Application Gateway can store SSL certificates as well as be integrated with the Azure Key Vault for using certificates from it.
love usage of the Let's Encrypt certificates on Application Gateway (uploading certificates to the Azure Application gateway HTTP Listener) works, Application Gateway supports the renewal of such certificates (uploading of a new version). But there is a problem... If you have some specific requirements (which I had). There should be some magic applied to get the certificate Expiration date and the CN domain name from the certificate in App Gateway.
Unfortunately, that magic works not always and sometimes provides wrong values that may lead to certificate expiration without you being aware of it.
Initial solution description
I was looking for a solution to automatically renew certification and found the following post from David Rodríguez. This post was extremely useful for me to start my challenge.
I followed the aforementioned article with some small exceptions. I will describe some steps from that article briefly here, however for full information just go to the origin, it is provided there, in a very nice and detailed way.
Step 1: Create a storage account with a public container
The container will host files that have to be uploaded there before Let's Encrypt performs a domain ownership check.
This is just the usual storage account with the publicly available blob container
You can upload some static dummy HTML file to that folder in order to be able to test that path later
Step 2: update Application Gateway to support domain ownership check
In this article, I assume you already have configured Application Gateway and all the appropriate DNS bindings are done, so that your hostname is resolving to the Application Gateway public IP address.
We need to forward certain requests that will be done by Let's Encrypt during the HTTP-01 challenge to the storage account that was created in the previous step.
Note: that has to be an HTTP request, not HTTPS.
Make sure Application Gateway listens to HTTP (port 80).
Update appropriate rule for that listener with the following path-based redirect rule:
Target URL will be the URL of your storage account container, created earlier.
Step 3: create Let's Encrypt certificate and add it to Application Gateway
First I used certbot on my WSL2 with Ubuntu 18.04. However, it appeared for me to be easier to use the ACME-PS module for PowerShell. See the example script that is using that module here.
As an output of this PowerShell script, you'll get the SSL certificate which can be then uploaded and assigned to the Application Gateway listener.
Step 4: Create Azure Automation Account and the PowerShell runbook
This step is pretty good described in the initial article. There is also an example PowerShell script linked.
So far so good.
Running renewal runbook successfully renewed certificate (s) and I was happy.
However, in my task I had the following requirements:
- Application Gateway served was routing traffic for several websites (about 15), there were several listeners and each website had its own certificate (they had different domains).
- The SSL certificates Expiration date has to be checked every 2 days and if the certificate was about to expire in less than 14 days it had to be renewed.
With the second requirement, I got a problem after the first certificate renewal. I was not able to get the correct Expiration date of the certificate uploaded to Application Gateway, I also was not able to get the domain name.
The only way to get such data is by decrypting PublicCertData property from PSApplicationGatewaySslCertificate object:
In order to do that, there should be some black magic applied, which made me suspicious:
As a result, I have got the wrong NotAfter (Expiration) date as well as Subject (which didn't contain domain name)
I assume the issue was in the way how Application Gateway stores certificates and which data is available for that certificate once it is uploaded there. Most likely, I was getting the Expiration data and subject for either intermediate or root certificate but not for the one I needed.
This was the point I decided to change the approach slightly and take Azure Key vault into play.
Updated solution. Using Azure key vault
Azure Key vault supports renewing certificates as well as uploading of the newer versions, it has also a big bunch of other features that make its usage pretty handy.
How to update the initial solution for this?
Step 1: Upload initially created certificate(s) to Key Vault
This is an easy task, you need to have PFX files and appropriate certificate passwords.
Remember to name the certificate properly (e.g. to use some prefix to distinguish the certificate and selectively renew it in the automation runbook. All that of course depends on your requirements).
In my case, I have the "LetsEncrypt-" prefix in certificate names.
Step 2: Enable Application Gateway to get certificates from Key vault
There are a couple of steps to be done in order to grant Application Gateway permissions to use certificates from Azure Key Vault.
There should be a managed identity created for this. There is documentation describing those steps in PowerShell. It can be also configured in Azure Portal
Step 3: Configure Application Gateway listener to use the certificate from Key Vault
Step 4: Do some minor changes to the renewal runbook and make it work with Azure Key Vault.
Now renewal runbook should fetch certificates from Azure Key Vault, check their properties if needed. As I mentioned above in my case it was important to get the Expiration date and domain name.
Getting certificate data from Key Vault is very easy:
Certificate renewal should happen if needed (e.g. if the certificate is about to expire soon) and then it can be uploaded as a new version of the existing certificate to the Key Vault.
Application Gateway will pick-up automatically newer certificate version within the next 4 hours.
Here are examples of the runbook for direct certificate renewal on the Application gateway and the improved one for Azure Key vault:
Pay attention to Let's Encrypt limitation
In the project I was working on, I had about 15 websites with different domain. There was 15 certificates (which were issued at the same day) and when I was trying to renew them all in the same time using the runbook mentioned above, I got the following error:
Normally that should not be an issue, since the rest of the certificates will be then renewed during the next run (assuming the next run happens after a certain time which is defined in the Let's Encrypt limitation. For example that can be the next day).
Let's Encrypt SSL certificates can be easily used on Application Gateway. In my opinion, it is always better to use Azure Key Vault for certificate storage.
Thanks to the integration between Application Gateway and Azure Key Vault it is very easy to use those certificates as well as renew them automatically.