sjkp/letsencrypt-siteextension

Renewal of certificate fails with specific rewrite rule

sebastianzolg opened this issue · 2 comments

Hello all,

Having the below rewrite rules in place, the renewal of the certificates fail.

When I disable the second rewrite rule Redirect subdomain digital.foo.de, the renewal completes successfully.

I always had the feeling that my rewrite rule isn't correctly modeled; however, I haven't found a better way of doing it.

So, my question is: Is this is a bug, or do I need to change my rewrite rules?

Thank you!

Functions.RenewCertificate

Microsoft.Azure.WebJobs.Host.FunctionInvocationException: Microsoft.Azure.WebJobs.Host.FunctionInvocationException : 

Exception while executing function: Functions.RenewCertificate ---> System.Exception : Unable to complete challenge with Lets Encrypt servers error was: {"type":"http-01","url":"https://acme-v02.api.letsencrypt.org/acme/chall-v3/XXXXXXX","status":"Invalid","validated":null,"error":{"Type":"urn:ietf:params:acme:error:unauthorized","Detail":"Invalid response from https://foo.de/digital [13.69.68.7]: \"<!doctype html><html><head><meta charset=\\\"utf-8\\\"><meta name=\\\"description\\\" content=\\\"\\\">

<meta name=\\\"viewport\\\" content=\\\"width=device\"","Identifier":null,"Subproblems":null,"Status":403},"errors":null,"token":"dhQTchFtwRtFov6cThlinDMSvC8RgN-XbUaAXsDwUsw","keyAuthorization":null} at async LetsEncrypt.Azure.Core.Services.AcmeService.RequestCertificate() at D:\a\1\s\LetsEncrypt.SiteExtension.Core\Services\AcmeService.cs : 87 at async LetsEncrypt.Azure.Core.CertificateManager.RequestInternalAsync(IAcmeConfig config) at D:\a\1\s\LetsEncrypt.SiteExtension.Core\CertificateManager.cs : 206 at async LetsEncrypt.Azure.Core.CertificateManager.RequestAndInstallInternalAsync(IAcmeConfig config) at D:\a\1\s\LetsEncrypt.SiteExtension.Core\CertificateManager.cs : 230 at async LetsEncrypt.Azure.Core.CertificateManager.RenewCertificate(Boolean skipInstallCertificate,Int32 renewXNumberOfDaysBeforeExpiration) at D:\a\1\s\LetsEncrypt.SiteExtension.Core\CertificateManager.cs : 172 at async LetsEncrypt.SiteExtension.Functions.RenewCertificate(TimerInfo timerInfo) at D:\a\1\s\LetsEncrypt.SiteExtension.WebJob\Functions.cs : 68 at async Microsoft.Azure.WebJobs.Host.Executors.VoidTaskMethodInvoker`2.InvokeAsync[TReflected,TReturnType](TReflected instance,Object[] arguments) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionInvoker`2.InvokeAsync[TReflected,TReturnValue](Object instance,Object[] arguments) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.InvokeAsync(IFunctionInvoker invoker,ParameterHelper parameterHelper,CancellationTokenSource timeoutTokenSource,CancellationTokenSource functionCancellationTokenSource,Boolean throwOnTimeout,TimeSpan timerInterval,IFunctionInstance instance) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithWatchersAsync(IFunctionInstance instance,ParameterHelper parameterHelper,TraceWriter traceWriter,CancellationTokenSource functionCancellationTokenSource) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(??) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(??) End of inner exception at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(??) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.TryExecuteAsync(IFunctionInstance functionInstance,CancellationToken cancellationToken)

Rewrite Rules

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Disable Azure Domain" patternSyntax="Wildcard" stopProcessing="true">
                    <match url="*" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{HTTP_HOST}" pattern="*.azurewebsites.net" />
                    </conditions>
                    <action type="Redirect" url="https://foo.de{REQUEST_URI}" redirectType="Permanent" />
                </rule>
                <rule name="Redirect subdomain digital.foo.de" enabled="true" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions>
                        <add input="{HTTP_HOST}" pattern="digital\.foo\.de" />
                    </conditions>
                    <action type="Redirect" url="https://foo.de/digital.html" />
                </rule>               
            </rules>
        </rewrite>
    </system.webServer>
</configuration> 

TLDR You need to change your rewrite rule to exclude the ACME challenge response, or use a different challenge that doesn't rely on HTTP (in this case, the DNS challenge).

The HTTP challenge expects a response in a URL like this: http://digital.foo.de/.well-known/acme-challenge/someRandomString. The extension does put it there, but your rewrite rule effectively makes it inaccessible (more precisely, it changes its contents to an HTML which is not the proper ACME response). If you exclude /.well-known/acme-challenge/* from your rewrite it should work.

Alternatively, you can use a challenge that doesn't use HTTP at all, in this case the DNS challenge. You can't use this extension for the DNS challenge though. You have two options:

  1. Simon's vNext Azure Function based solution: https://github.com/sjkp/letsencrypt-azure
  2. My WebJob based solution (built on top of the above): https://github.com/ohadschn/letsencrypt-webapp-renewer

You can see the full error in the URL specified by Unable to complete challenge with Lets Encrypt servers error was: {"type":"http-01","url": (note that it contains your actual domain, which you seemingly wanted to scrub, so pay attention when sharing it).

Brilliant answer, @ohadschn!
Thank you very much for your advice and help!
It's all clear to me, and I'll adjust the rewrite rules accordingly.
Take care!
—Sebastian