Wildcard name-based provisioning in haproxy.cfg with LE

haproxylets-encryptredirection

Trying to redirect certain domain names to specific backends using catchall wildcard entry[A, A1] in haproxy.cfg (HAproxy v1.8.12 Host OS:Alpine Linux v3.9 version details [B] posted at the end in case it has to do with different versions behaving differently[C]):

frontend http
        bind *:80
        reqadd X-Forwarded-Proto:\ http
        redirect scheme https code 301 if !{ ssl_fc }
        ### specific domains ###
        acl host_specificdomain hdr(host) -m reg -i  ^[^\.]+\.specific.domain$
        #Redirect all other domains with all extensions and all ports to:
        acl host_catchall hdr(host) -m reg -i ^[^\.]+\.^[^\.].^[^\.](:[0-9]+)?$
        ### clusters ###
        use_backend specificdomain_cluster if host_specificdomain
        use_backend catchall_cluster if host_catchall

frontend https
        bind       *:443 ssl no-sslv3 crt-list /etc/haproxy/ssl/ssl.lst
        rspadd Strict-Transport-Security:\ max-age=31536000;\ includeSubDomains;\ preload
        rspadd X-Frame-Options:\ DENY
        reqadd X-Forwarded-Proto:\ https
        acl app_letsencrypt  path_beg   /.well-known/acme-challenge/
        use_backend bk-letsencrypt if app_letsencrypt

backend bk-letsencrypt
    log global
    mode http
    server srv_letsencrypt 127.0.0.1:63443

backend catchall_cluster
        redirect scheme https if !{ ssl_fc }
        mode http
        balance roundrobin
        option forwardfor
        server server1 192.168.200.101:80 check
        server server2 192.168.200.102:80 check

backend specificdomain_cluster
        redirect scheme https if !{ ssl_fc }
        mode http
        balance roundrobin
        option forwardfor
        server server1 192.168.200:103:80 check
        server server2 192.168.200.104:80 check 

haproxy stats shows all servers alive, fyi.

The above posed three issues:

  1. The catchall domains option with wildcards (acl host_catchall hdr(host) -m reg -i ^[^.]+.^[^.].^^.?$) did not redirect all but not specific.domain (acl host_specificdomain hdr(host) -m reg -i ^[^.]+.specific.domain$).

  2. Even the specific.domain gives 503 error "service unavailable, no server is available to handle this request" when:

$ curl -v --resolve specific.domain:443:127.0.0.1 https://specific.domain
* Expire in 0 ms for 6 (transfer 0x55c2bc8bc4c0)
* Added specific.domain:443:127.0.0.1 to DNS cache
* Hostname specific.domain was found in DNS cache
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x55c2bc8bc4c0)
* Connected to specific.domain (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt   CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=specific.domain
*  start date: Jun 17 08:00:34 2019 GMT
*  expire date: Sep 15 08:00:34 2019 GMT
*  subjectAltName: host "specific.domain" matched cert's "specific.domain"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: specific.domain
> User-Agent: curl/7.64.0
> Accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* HTTP 1.0, assume close after body < HTTP/1.0 503 Service Unavailable < Cache-Control: no-cache < Connection: close < Content-Type: text/html <  <html><body><h1>503 Service Unavailable</h1> No server is available to handle this request. </body></html>
* TLSv1.3 (IN), TLS alert, close notify (256):
* Closing connection 0

The same domain was readily available over http as follows:

$ curl -v --resolve specific.domain:80:127.0.0.1 http://specific.domain
* Expire in 0 ms for 6 (transfer 0x55cecf76c4c0)
* Added specific.domain:80:127.0.0.1 to DNS cache
* Hostname specific.domain was found in DNS cache
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x55cecf76c4c0)
* Connected to specific.domain (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: specific.domain
> User-Agent: curl/7.64.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 22 Jun 2019 18:58:31 GMT
< Content-Type: text/html
< Content-Length: 17832
< Last-Modified: Wed, 19 Jun 2019 09:19:57 GMT
< Connection: close
< Vary: Accept-Encoding
< ETag: "5d09fe3d-45a8"
< Accept-Ranges: bytes
< 
<!doctype html>
...
  1. Haproxy fails to pull additional domain certs which were pointed by DNS to the specific IP of the machine:
Failed authorization procedure. www.specific.domain (http-01): urn:ietf:params:acme:error:unauthorized :: The client lacks sufficient authorization :: Invalid response from http://www.specific.domain/.well-known/acme-challenge/APRETTYLONGID [PUBLIC IP]: "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n   \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<ht"

Btw, I am using a script [D] to generate LE certs, fyi.

Any pointers appreciated,

Thanks and cheers,
/z

References:

A. https://serverfault.com/a/909483/528889

A1. https://unix.stackexchange.com/questions/166169/how-to-configure-haproxy-to-redirect-multiple-domain

B. HAproxy version details:

 haproxy -vvvvv
HA-Proxy version 1.8.12-8a200c7 2018/06/27
Copyright 2000-2018 Willy Tarreau <willy@haproxy.org>

Build options :
  TARGET  = linux2628
  CPU     = generic
  CC      = gcc
  CFLAGS  = -Os -fomit-frame-pointer
  OPTIONS = USE_ZLIB=1 USE_OPENSSL=1 USE_LUA=1 USE_PCRE=1 USE_NS=1

Default settings :
  maxconn = 2000, bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with OpenSSL version : OpenSSL 1.1.1a  20 Nov 2018
Running on OpenSSL version : OpenSSL 1.1.1b  26 Feb 2019
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
Built with Lua version : Lua 5.3.5
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Encrypted password support via crypt(3): yes
Built with multi-threading support.
Built with PCRE version : 8.42 2018-03-20
Running on PCRE version : 8.42 2018-03-20
PCRE library supports JIT : no (USE_PCRE_JIT not set)
Built with zlib version : 1.2.11
Running on zlib version : 1.2.11
Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip")
Built with network namespace support.

Available polling systems :
      epoll : pref=300,  test result OK
       poll : pref=200,  test result OK
     select : pref=150,  test result OK
Total: 3 (3 usable), will use epoll.

Available filters :
        [SPOE] spoe
        [COMP] compression
        [TRACE] trace

C. https://superuser.com/questions/1195012/haproxy-1-7-2-refuses-to-match-host-in-acl

D. https://github.com/tisc0/letsHAP

Best Answer

First of all, your https frontend lack of backend.

Also, my advice is to use a default backend in your frontends, rather than a complicated regex to catch "all the others domains".

And about acls with regex on host header, I've been more successful with hdr_reg(host) than hdr(host) -m reg -i.

I would propose you another piece of advice: - make things less complex - comment unnecessary options to facilitate troubleshooting

Ah : I'm not sure your regex lines are read in http frontend, since you used the redirect scheme https before : I would say all your request go to https, which has no backend.

Hope it helps.

Cheers,

Related Topic