Allow Certificate Renewals When Using Client Certificate Authentication

Using Apache virtual hosts, I have client certificate authentication setup for a number of my domains that also use Let's Encrypt certificates. This all works great, but certbot is not able to renew the certificates because there is no client certificate presented.

I have a virtual host on port 80 that permanently redirects all requests to the virtual host on 443, but I'm not sure how to allow certbot to renew/issue certificates.

Here's an example of my apache config:

<VirtualHost *:80>
    ServerName subdomain.foo.com
    Redirect permanent / https://subdomain.foo.com/
</VirtualHost>

<VirtualHost *:443>
        ServerAdmin webmaster@foo.com
        ServerName subdomain.foo.com
        
        Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
        Header always set X-XSS-Protection "1; mode=block"
        Header always set X-Content-Type-Options "nosniff"
        Header always set X-Frame-Options "SAMEORIGIN"
        Header always set Referrer-Policy "no-referrer-when-downgrade"
        Header always set X-Permitted-Cross-Domain-Policies "none"

        SSLProxyEngine on
        ProxyVia on
        ProxyAddHeaders on
        ProxyPreserveHost on

        SecRuleEngine On

        RewriteEngine On

        RewriteRule /.well-known/acme-challenge/ - [R,L]
        Alias /.well-known/acme-challenge /web/letsencrypt/.well-known/acme-challenge

        <Directory /web/letsencrypt>
               Require all granted
        </Directory>

        ErrorLog "/logs/subdomain.foo.com-error_log"
        CustomLog "/logs/subdomain.foo.com-access_log" combined

        SSLEngine on
        SSLProtocol TLSv1.2
        SSLHonorCipherOrder on
        SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256

        SSLVerifyClient require
        SSLVerifyDepth 1
        SSLCACertificateFile "/keys/rootCA.pem"
        SSLCARevocationFile "/keys/rootCA.crl"
        SSLCARevocationCheck chain

        SSLCertificateFile "/etc/letsencrypt/live/subdomain.foo.com/fullchain.pem"
        SSLCertificateKeyFile "/etc/letsencrypt/live/subdomain.foo.com/privkey.pem"

        UseCanonicalName on
        ProxyPreserveHost on
        CacheStaleOnError on
        RemoteIPHeader X-Forwarded-For
        ProxyRequests Off

        AllowEncodedSlashes NoDecode
        ProxyPass /.well-known !
        ProxyPass / http://myproxy:18495/ nocanon
        ProxyPassReverse / http://myproxy:18495/

        BrowserMatch ".*MSIE.*" \
        nokeepalive ssl-unclean-shutdown \
        downgrade-1.0 force-response-1.0

        CustomLog "/logs/ssl_request_log" \
        "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</VirtualHost>

Here's my renewal script for Let's Encrypt:

# renew_before_expiry = 30 days
version = 2.1.0
archive_dir = /etc/letsencrypt/web/archive/subdomain.foo.com
cert = /etc/letsencrypt/web/live/subdomain.foo.com/cert.pem
privkey = /etc/letsencrypt/web/live/subdomain.foo.com/privkey.pem
chain = /etc/letsencrypt/web/live/subdomain.foo.com/chain.pem
fullchain = /etc/letsencrypt/web/live/subdomain.foo.com/fullchain.pem

# Options used in the renewal process
[renewalparams]
account = MYACCOUNTNUMBER
rsa_key_size = 4096
config_dir = /etc/letsencrypt/web
authenticator = webroot
webroot_path = /web/letsencrypt,
server = https://acme-v02.api.letsencrypt.org/directory
post_hook = /certbot-web-hook
key_type = rsa

[[webroot_map]]
subdomain.foo.com = /web/letsencrypt

My recommendation: handle all the /.well-known/acme-challeng/ stuff in the HTTP (port 80) virtualhost and never let it redirect to HTTPS to begin with.

5 Likes

I have tried that in a few different way but none seem to work. Here's an example, replacing the

Redirect permanent / https://subdomain.foo.com/

with

RedirectMatch permanent ^/(?!\.well-known/acme-challenge/.*)(.*) https://subdomain.foo.com/$1

1 Like

If you only exempt the acme-challenge path from the redirect, then you're lacking more directives in your HTTP vhost. E.g., you're using the webroot authenticator, but the HTTP vhost doesn't have any documentroot.

4 Likes

OK, fair enough. Can you please provide the solution so I can try it? Bearing in mind that this all worked prior to setting up client certificate authentication. That's the only thing blocking it.

Also, this is a docker setup for Apache httpd and I am volume mapping for LE via

- /etc/letsencrypt/web:/etc/letsencrypt:ro
- /web/letsencrypt:/web/letsencrypt:rw

Probably just move all the acme-challenge related stuff like that Alias to the HTTP vhost?

6 Likes

Yes! Moving the acme stuff to the port 80 vhost does allow it to work. Thank you.

This is what my vhost on port 80 looks like now:

<VirtualHost *:80>
        ServerName subdomain.foo.com

        Alias /.well-known/acme-challenge /web/letsencrypt/.well-known/acme-challenge

        <Directory /web/letsencrypt>
                Require all granted
        </Directory>

        RedirectMatch permanent ^/(?!\.well-known/acme-challenge/.*)(.*) https://subdomain.foo.com/$1
</VirtualHost>
5 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.