SSL certificates eh. We all want them, but as an industry, I think we're coming round to the conclusion that we shouldn't have to pay for them to protect users browsing the web.

Having decided that paying money for SSL certificates is basicallly a waste, we wanted to use Let's Encrypt for a bunch of Azure web apps (asp.net core), and static marketing site.

Now, Let's encrypt is not exactly a first class citizen in Azure, but it's doable with a bit of messing about, thanks to the efforts of Simon Pederson and the Let's encrypt Site Extension.

For simple setups, there are a lot of blogs and guides to getting this up and running - Scott Hanselman has a great guide here. We faced some problems with this simplistic solution though, due to our more complex requirements:

  • Multiple sites (Some asp.net core, some static html sites), so we didn't want to install multiple site extensions
  • We use the local cache feature of Azure, so until recently, the site extension didn't support this
  • Load balancing via Cloudflare - so when the challenge file was requested by lets encrypt, we weren't sure which origin server would pick up that request.

So, here's how we got it working.

1 - Install the site extension.

Ok so the idea here is that instead of installing the site extension for every azure app service that you want to use let's encrypt on, you install it as a standalone service, and use it to install certificates for your apps. You then create a function app in azure to periodically call through to the site extension, and get your certs on.

This is documented here, so following those steps, you should have an app service that contains an API 👍

2 - Use the new API endpoint to enable blob storage

In that blog post, Simon uses the endpoint letsencrypt/api/certificates/challengeprovider/http/kudu/certificateinstall/azurewebapp. However, version 0.8.9 of the site extension now allows you to store the let's encrypt challenge in an azure storage account as a blob file. This is very cool because it solves our load balancing and local cache problem - all we need to do is make sure that when the let's encrypt challenge comes in that we direct it to the blob.

So - create a storage account for the challenge files, and then grab the connection string for that storage account and create a new app setting in your let's encrypt app service called letsencrypt:AuthorizationChallengeBlobStorageAccount, and set it to that connection string.

Then, in your code that calls the site extension API, rather than call letsencrypt/api/certificates/challengeprovider/http/kudu/certificateinstall/azurewebapp, change that to letsencrypt/api/certificates/challengeprovider/http/blob/certificateinstall/azurewebapp.

3 - Setup the web apps to redirect to the blob storage.

Almost there now. When you run the function app, you'll get an error message saying that The Lets Encrypt ACME server was probably unable to reach XXX. This is because the ACME server is trying to verify the request by making a request to the challenge file... but that challenge file is now in blob storage.

All you need to do is create some redirect rule in your app so that the requests are passeed to the correct blob account. In asp.net core, you can do something like this:

var options = new RewriteOptions()
.AddRedirect("^.well-known/acme-challenge/(.*)", "https://YOURSTORAGECCOUNT.blob.core.windows.net/letsencrypt-siteextension/.well-known/acme-challenge/$1");

app.UseRewriter(options);

If you're in IIS, your web.config has your back with a rewrite rule:

<rule name="Acme challenge" stopProcessing="true">
    <match url="^.well-known/acme-challenge/(.+)?" />
    <action type="Redirect" url="https://YOURSTORAGEACCOUNT.blob.core.windows.net/letsencrypt-siteextension/.well-known/acme-challenge/{R:1}" redirectType="Permanent" />
</rule>                

Done 🚀