In the previous post, we used the Azure Function to generate SSL certificates for an Azure App Service. I wanted it to generate certificates for an Azure Function App, but that failed.

Why did it fail?

The Let's Encrypt Site Extension does all the heavy lifting of talking to the Let's Encrypt API's. Part of this request flow is the validation of the domain. The site extension does this by placing a file with a code on your site, in the folder wwwroot/.well-known/acme-challenge.
The Let's Encrypt API then accesses that file through a standard HTTP request, https://yoururl/.well-known/acme-challenge/{code}. Your site serves the content of the file and this gets matched to the code supplied to the API. If it matches, you get a new certificate!

In Azure App Services this works without a problem, but on Azure Functions this url is not accessible so it cannot be validated and you don't get a certificate. The folder with the codes is created, so we just needed a way to serve it.

Now the workaround!

First you need to create an additional Function in the Function App that requires the SSL certificates. In my case, it's the same Function App that also supplies the certificates. It needs to be an HttpTrigger, with Anonymous access level and I named it LetsEncryptChallenge.
Update the function.json file so that it looks like this (notice the addition of the rout parameter):

{
  "bindings": [
    {
      "authLevel": "anonymous",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in",
      "methods": [
        "get"
      ],
      "route": "LetsEncryptChallenge/{code}"
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ]
}


The code for this Function is simple:

using Microsoft.AspNetCore.Mvc;
using System.Text;

public static IActionResult Run(HttpRequest req, string code, ILogger log, ExecutionContext ec)
{    
    var content = File.ReadAllText($"{ec.FunctionAppDirectory}\\.well-known\\acme-challenge\\{code}");
    return new OkObjectResult(content);
}

It's only purpose is accepting the variable part of the request and serving the contents of the associated file. The url for this function would be https://yourappname/api/letsencryptchallenge/{code} but the API will be trying to access https//yourappname/.well-known/acme-challenge/{code}.

I solved this by creating a Proxy in the Function App, named LetsEncryptChallengeRedirect. As a route template, put .well-known/acme-challenge/{code} and limit it to just GET requests. Put https://localhost/api/LetsEncryptChallenge/{code} as a Backend URL and all the requests get redirected correctly to your Azure Function.

All that's left now is adding those custom domains to the Azure Function App and updating the code from the previous post to request certificates for them!


Ghost Azure Functions Blog Let's Encrypt SSL