Using Let’s Encrypt SSL Certificates with Azure Functions

In Azure Web Apps I’ve set up previously I’ve managed to get SSL certificates for the custom domains used by using the “Let’s Encrypt” site extension in the kudu management portal as detailed here.

I’d hoped it would be as easy to do this with Azure Functions as with the Azure Web Apps but unfortunately there’s a few extra hoops to jump through first to get everything working.

The web job checks that you own the site that you’re requesting the certificate for by placing a file in the folder .well-known/acme-challenge and then Let’s Encrypt confirms the value in this file is what it’s expecting. The problem is that while Let’s Encrypt is looking for a file such as http://myazurefunction.azurewebsites.net/.well-known/acme-challenge/dPWkgxMrvKH64AoVIudF_ggnhyStN7LRV1kdzVPajSM this doesn’t map to a known function and so is inaccessible as functions have default routing of the type http://myazurefunction.azurewebsites.net/api/functionname.

In order to get around this it’s necessary to create a function to return a file with the desired name and to create a proxy to point the path expected by Let’s Encrypt to this file server function.

The below solution can be seen in full here and is based on that detailed here. My version differs from that provided in the wiki because I am using .NET Core functions rather than Framework ones and I have created these in Visual Studio rather than through the portal.

Function – LetsEncrypt.cs
	
public static class LetsEncrypt
{
	[FunctionName("LetsEncrypt")]
	public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "LetsEncrypt/.well-known/acme-challenge/{code}")]HttpRequest req, string code, TraceWriter log)
	{
		log.Info($"C# HTTP trigger function processed a request. {code}");

		var content = File.ReadAllText(@"D:\home\site\wwwroot\.well-known\acme-challenge\" + code);
		var resp = new HttpResponseMessage(HttpStatusCode.OK);
		resp.Content = new StringContent(content, Encoding.UTF8, "text/plain");
		return resp;
	}
}
	
Proxy – proxies.json
	
{
  "$schema": "http://json.schemastore.org/proxies",
  "proxies": {
    "LetsEncryptProxy": {
      "matchCondition": {
        "route": "/.well-known/acme-challenge/{code}"
      },
      "backendUri": "http://%WEBSITE_HOSTNAME%/api/letsencrypt/.well-known/acme-challenge/{code}"
    }
  }
}
	

One thing to note is that the proxy and the function need to have unique names as they exist in the same namespace. The proxies.json file should also have its “Copy to Output Directory” setting set to “Copy if newer” so that it’s copied up to Azure.

Once the above files have been created the proxy will redirect the Let’s Encrypt file check to the function which will serve up the file with the name provided in the URL parameter.


Leave a Reply

Your email address will not be published. Required fields are marked *