sjkp / letsencrypt-siteextension

Azure Web App Site Extension for easy installation and configuration of Let's Encrypt issued SSL certifcates for custom domain names.
745 stars 76 forks source link

[Azure Function NODEJS] The Lets Encrypt ACME server was probably unable to reach #276

Open fildanrcs opened 5 years ago

fildanrcs commented 5 years ago

Hi, I've read a lot of issues out there but I have not found one similar to my case. What I'm having trouble with is that with Azure Functions, after I've configured the function and the proxy, I trigger the extension (No Job) and then I see the call coming to the function and the proper code catched in the function. But I get this log in the function:

2018-12-12T15:00:24.975 [Information] ACME challenge response file 'D:\home\site\wwwroot.well-known\acme-challenge\BKbHYP_KvC6AO7BLfQY2ZJUN-CR_j-gWa700EsRmuUI' not found.

and the result of the extension links to this error:

{
  "identifier": {
    "type": "dns",
    "value": "mysubdomain.mydomain.com"
  },
  "status": "invalid",
  "expires": "2018-12-19T15:00:05Z",
  "challenges": [
    {
      "type": "http-01",
      "status": "invalid",
      "error": {
        "type": "urn:acme:error:unauthorized",
        "detail": "The key authorization file from the server did not match this challenge [BKbHYP_KvC6AO7BLfQY2ZJUN-CR_j-gWa700EsRmuUI.wnp07H8xywSb29RajsY9IT_uZYHQd_20GJGfiEDouoI] != []",
        "status": 403
      },
      "uri": "https://acme-staging.api.letsencrypt.org/acme/challenge/CMSt4ykFxDhHXiPE2qQdcHQlOSzlJMbchsv4T7C54_c/208039685",
      "token": "BKbHYP_KvC6AO7BLfQY2ZJUN-CR_j-gWa700EsRmuUI",
      "validationRecord": [
        {
          "url": "http://mysubdomain.mydomain.com/.well-known/acme-challenge/BKbHYP_KvC6AO7BLfQY2ZJUN-CR_j-gWa700EsRmuUI",
          "hostname": "mysubdomain.mydomain.com",
          "port": "80",
          "addressesResolved": [
            "40.114.210.78"
          ],
          "addressUsed": "40.114.210.78"
        }
      ]
    },
    {
      "type": "dns-01",
      "status": "invalid",
      "uri": "https://acme-staging.api.letsencrypt.org/acme/challenge/CMSt4ykFxDhHXiPE2qQdcHQlOSzlJMbchsv4T7C54_c/208039686",
      "token": "Gv_xLZOrNVtERqen82r660HBmOzHLSZprNRfhtOY4Do"
    },
    {
      "type": "tls-alpn-01",
      "status": "invalid",
      "uri": "https://acme-staging.api.letsencrypt.org/acme/challenge/CMSt4ykFxDhHXiPE2qQdcHQlOSzlJMbchsv4T7C54_c/208039687",
      "token": "AEHGDHZiWxy-KeJzr7BsIExaNg1PZfLgbdOfFmXlYls"
    }
  ],
  "combinations": [
    [
      2
    ],
    [
      1
    ],
    [
      0
    ]
  ]
}

This is the code of the function "renewcert":

const fs = require('fs');

module.exports = function(context, request) {
  let code = request['query'].letscode;
  const responseFilePath = "D:\\home\\site\\wwwroot\\.well-known\\acme-challenge\\${code}";

  context.log("Checking for ACME challenge response at '${responseFilePath}'...");

  fs.exists(responseFilePath, (exists) => {
    if (!exists) {
      context.log("ACME challenge response file '${responseFilePath}' not found.");
      context.done(null, {
        status: 404,
        headers: { "Content-Type": "text/plain" },
        body: 'ACME challenge response not found.'
      });
      return;
    }

    context.log("ACME challenge response file '${responseFilePath}' found. Reading file...");
    fs.readFile(responseFilePath, (error, data) => {
      if (error) {
        context.log.error("An error occured while reading file '${responseFilePath}'.", error);
        context.done(null, { status: 500 });
        return;
      }

      context.log("ACME challenge response file '${responseFilePath}' read successfully.");
      context.done(null, {
        status: 200,
        headers: { "Content-Type": "text/plain" },
        body: data
      });
    });
  });
};

And the proxy JSON:

"LetsEncryptProxy": {
          "matchCondition": {
            "route": "/.well-known/acme-challenge/{code}"
          },
          "backendUri": "https://azfunctionresourcename.azurewebsites.net/api/renewcert?code=AzUrEfUnCtIOnCoDe==",
          "requestOverrides": {
               "backend.request.querystring.letscode": "{code}"
           }
        }

It seems like the extension somehow is not able to create the file in acme-challenge folder. Can you please help me with this?

Thanks in advance!

ewild commented 5 years ago

I had this same problem and here is how I got it to work. First off, I think this only applies to Azure Functions so I'm not sure that there is much here for someone running a web app. My setup is a an Azure Function that uses proxies to call a different Storage account that is setup as a static website. When you click the button for the Lets Encrypt extension to get a certificate at some point in the process it loads a file onto your site that it then requests is from .well-known\acme-challenge. 2 problems, 1) I never saw a folder with that name in my static website, and 2) the real problem, Azure functions can't serve files without extensions, which that challenge file is. So I tried to see if there was a config setting that would allow Azure functions to serve files without extensions, but there is not one I can find. To fix it 2 things need to be done, and you can read more about it here https://matt-roberts.me/lets-encrypt-on-azure-appservices-with-local-cache/ . In your static website create a new container called 'letsencrypt-challenge', and then add these two entries in your Azure Function application settings

letsencrypt:AuthorizationChallengeBlobStorageAccount : your connection string to your static website storage container
letsencrypt:AuthorizationChallengeBlobStorageContainer : letsencrypt-challenge

next you need to create a proxy on your Azure function to the challenge folder like this:

Route Template: /.well-known/acme-challenge/{*restOfChallenge}
https://[yourstaticwebsitehere].blob.core.windows.net/letsencrypt-challenge/.well-known/acme-challenge/{restOfChallenge}

Once I did this, I could see that the Lets Encrypt extension was able to create a file in the new letsencrypt-challenge container that I had made, and it was able to serve up the extension-less file to complete the challenge.

mikezks commented 5 years ago

@ewild, thanks for referencing this blog post. It was down the moment I tried to reach the post, but still available in Google cache.

Especially helpful for me was the mentioned change of the URL:

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.

mikezks commented 5 years ago

@sjkp, could you please add this important information concerning the API Blob endpoint to the docs as well?

https://github.com/sjkp/letsencrypt-siteextension/wiki/Azure-Function,-Multi-Region,-Local-Cache-support

P.S.: Your site extension works perfectly - thanks a lot for all your work! 😃