How to use Let's Encrypt in Azure Web Apps running on .Net Core and Linux
TL; DR;
The Let's Encrypt Site Extension cannot be used for ASP.Net Core on a Linux Web App. This blog post shows one way to solve those problems.
Background
The Let's Encrypt Site Extension is an easy way to achieve getting and renewing SSL certificates for Web Apps.
However, site extensions are not available when running on Linux. Furthermore, the certificate creation process needs support from the target web app. With .Net Framework and IIS this could be achieved with web.config modification without modifying the web app (or it's web.config) itself.
The solution
This solution is based on the letsencrypt-webapp-renewer. It uses the same core library than the Azure Lets Encrypt site extension, but it is run as a WebJob. It can (should) be installed on its own web app, and supports multiple target websites.
The author of the letsencrypt-webapp-renewer has made thorough instructions, so I won't copy them here. When granting the service principal rights, you may want to only add Website Contributor and Web Plan Contributor instead of Contributor rights. The only thing needed in addition to those instructions is the support by the web app itself.
Implementing ACME support in the web app
The ACME process involves a step for authenticating target hostname. There are a few ways of achieving this, but the default in this scenario is to use the HTTP challenge. This involves making a certain data available in a certain URL. The letsencrypt-webapp-renewer handles putting the data to target web app's wwwroot folder via Kudu (see KuduRestClient), but it cannot make it available via HTTP itself. This can be easily implemented with the StaticFiles middleware from Microsoft.
Without further ado, here's the code as an extension method.
using System; using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.FileProviders; namespace Innofactor.Acme { public static class AcmeChallengeResponderExtensions { private const string WellKnownFolder = ".well-known"; private const string WellKnownRequestPath = "/.well-known"; private const string WellKnownContentType = "text/plain"; public static IApplicationBuilder UseAcmeChallengeResponder(this IApplicationBuilder app, string rootFolderPath) { if (rootFolderPath == null) throw new ArgumentNullException(nameof(rootFolderPath)); var root = new DirectoryInfo(rootFolderPath); if (!root.Exists) throw new ArgumentException("The provided folder does not exist"); var wellKnownFolder = root.CreateSubdirectory(WellKnownFolder); app.UseStaticFiles(new StaticFileOptions { RequestPath = WellKnownRequestPath, FileProvider = new PhysicalFileProvider(wellKnownFolder.FullName), ServeUnknownFileTypes = true, DefaultContentType = WellKnownContentType }); return app; } } }
Example use in Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //make sure this line is before any other handlers that could handle requests to /.well-known app.UseAcmeChallengeResponder(env.ContentRootPath); }
Troubleshooting
If you encounter any errors, make sure to look at the Web Job logs from the Web App you installed the letsencrypt-webapp-renewer Web Job.
Lauri works as the Research & Development Director with 10+ years of experience in software development and DevOps practices.