mirror of
https://codeberg.org/Codeberg/pages-server.git
synced 2024-11-18 10:29:43 +00:00
Add proof of concept for SNI-based routing through HAProxy
This commit is contained in:
parent
2e0608c270
commit
b54f9df4c5
12 changed files with 234 additions and 0 deletions
1
haproxy-sni/.gitignore
vendored
Normal file
1
haproxy-sni/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.dump
|
20
haproxy-sni/README.md
Normal file
20
haproxy-sni/README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# HAProxy with SNI & Host-based rules
|
||||||
|
|
||||||
|
This is a proof of concept, enabling HAProxy to use *either* SNI to redirect to backends with their own HTTPS certificates (which are then fully exposed to the client; HAProxy only proxies on a TCP level in that case), *as well as* to terminate HTTPS and use the Host header to redirect to backends that use HTTP (or a new HTTPS connection).
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
1. The `http_redirect_frontend` is only there to listen on port 80 and redirect every request to HTTPS.
|
||||||
|
2. The `https_sni_frontend` listens on port 443 and chooses a backend based on the SNI hostname of the TLS connection.
|
||||||
|
3. The `https_termination_backend` passes all requests to a unix socket (using the plain TCP data).
|
||||||
|
4. The `https_termination_frontend` listens on said unix socket, terminates the HTTPS connections and then chooses a backend based on the Host header.
|
||||||
|
|
||||||
|
In the example (see [haproxy.cfg](haproxy.cfg)), the `pages_backend` is listening via HTTPS and is providing its own HTTPS certificates, while the `gitea_backend` only provides HTTP.
|
||||||
|
|
||||||
|
## How to test
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
./test.sh
|
||||||
|
|
||||||
|
# For manual testing: all HTTPS URLs connect to localhost:443 & certificates are not verified.
|
||||||
|
./test.sh [curl-options...] <url>
|
||||||
|
```
|
8
haproxy-sni/dhparam.pem
Normal file
8
haproxy-sni/dhparam.pem
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-----BEGIN DH PARAMETERS-----
|
||||||
|
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
|
||||||
|
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
|
||||||
|
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
|
||||||
|
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
|
||||||
|
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
|
||||||
|
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
|
||||||
|
-----END DH PARAMETERS-----
|
22
haproxy-sni/docker-compose.yml
Normal file
22
haproxy-sni/docker-compose.yml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
haproxy:
|
||||||
|
image: haproxy
|
||||||
|
ports: ["443:443"]
|
||||||
|
volumes:
|
||||||
|
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||||||
|
- ./dhparam.pem:/etc/ssl/dhparam.pem:ro
|
||||||
|
- ./haproxy-certificates:/etc/ssl/private/haproxy:ro
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
gitea:
|
||||||
|
image: caddy
|
||||||
|
volumes:
|
||||||
|
- ./gitea-www:/srv:ro
|
||||||
|
- ./gitea.Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
pages:
|
||||||
|
image: caddy
|
||||||
|
volumes:
|
||||||
|
- ./pages-www:/srv:ro
|
||||||
|
- ./pages.Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
|
1
haproxy-sni/gitea-www/index.html
Normal file
1
haproxy-sni/gitea-www/index.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello to Gitea!
|
3
haproxy-sni/gitea.Caddyfile
Normal file
3
haproxy-sni/gitea.Caddyfile
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
http://codeberg.org
|
||||||
|
|
||||||
|
file_server
|
26
haproxy-sni/haproxy-certificates/codeberg.org.pem
Normal file
26
haproxy-sni/haproxy-certificates/codeberg.org.pem
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEUDCCArigAwIBAgIRAMq3iwF963VGkzXFpbrpAtkwDQYJKoZIhvcNAQELBQAw
|
||||||
|
gYkxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEvMC0GA1UECwwmbW9t
|
||||||
|
YXJAbW9yaXR6LWxhcHRvcCAoTW9yaXR6IE1hcnF1YXJkdCkxNjA0BgNVBAMMLW1r
|
||||||
|
Y2VydCBtb21hckBtb3JpdHotbGFwdG9wIChNb3JpdHogTWFycXVhcmR0KTAeFw0y
|
||||||
|
MTA2MDYwOTQ4NDFaFw0yMzA5MDYwOTQ4NDFaMFoxJzAlBgNVBAoTHm1rY2VydCBk
|
||||||
|
ZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEvMC0GA1UECwwmbW9tYXJAbW9yaXR6LWxh
|
||||||
|
cHRvcCAoTW9yaXR6IE1hcnF1YXJkdCkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||||
|
ggEKAoIBAQCrSPSPM6grNZMG4ZKFCVxuXu+qkHdzSR96QUxi00VkIrkGPmyMN7q7
|
||||||
|
rUQJto9C9guJio3n7y3Bvr5kBjICjyWQd7GfkVuYgiYiG/O2hy1u1dIMCAB/Zhx1
|
||||||
|
F1mvRfn/Q4eZk2GSOUM+kC0xaNsn2827VGLOGFywUhRmu7J9QSQ3x1Pi5BME7eNC
|
||||||
|
AKup0CbrMrZSzKAEuYujLY0UYRxUrguMnV60wxJDCYE14YDxn9t0g7wQmzyndupk
|
||||||
|
AMLNJZX5L83RA6vUEuTVYBFcyB0Fu3oBLQ31y5QOZ7WF/QiO5cPicQJI/oyXlHq4
|
||||||
|
97BWS/H28kj1H5ZM8+5yhCYDtgj7dERpAgMBAAGjYTBfMA4GA1UdDwEB/wQEAwIF
|
||||||
|
oDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSOSXQZqt2gjbTOkE9Q
|
||||||
|
ddI8SYPqrDAXBgNVHREEEDAOggxjb2RlYmVyZy5vcmcwDQYJKoZIhvcNAQELBQAD
|
||||||
|
ggGBAJ/57DGqfuOa3aS/nLeAzl8komvyHuoOZi9yDK2Jqr+COxP58zSu8xwhiZfc
|
||||||
|
TJvIyB9QR7imGiQ7fEKby40q8uxGGx13oY7gQy7PG8hHk2dkfDZuSQacnpPRC3W0
|
||||||
|
0dL2CQIog6rw6jJHjxneitkX9FUmOnHIKy7LHya0Sthg36Z0Qw5JA3SCy6OQNepR
|
||||||
|
R2XzwTZ0KFk6gAuKCto8ENUlU5lV9PM4X3U0cBOIc5LJAPM+cxEDUocFtFqKJPbe
|
||||||
|
YYlSeB200YhYOdi+x34n9xnQjFu/jVlWF+Y0tMBB1WWq6rZbnuylwWLYQZAo10Co
|
||||||
|
D3oWsYRlD/ZL7X20ztIy8vRXz33ugnxxf88Q7csWDYb4S325svLfI2EjciIxYmBo
|
||||||
|
dSJxXRQkadjIoI7gNvzeWBkYSJpQUbaD4nT2xRS8vfuv42/DrIehb8SbTivHmcB3
|
||||||
|
OibpWIvDtS1B8thIlzl0edb+8pb6mof7pOBxoZdcBsSAk2/48s+jfRHfD9XcuKnv
|
||||||
|
hGCdSQ==
|
||||||
|
-----END CERTIFICATE-----
|
28
haproxy-sni/haproxy-certificates/codeberg.org.pem.key
Normal file
28
haproxy-sni/haproxy-certificates/codeberg.org.pem.key
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrSPSPM6grNZMG
|
||||||
|
4ZKFCVxuXu+qkHdzSR96QUxi00VkIrkGPmyMN7q7rUQJto9C9guJio3n7y3Bvr5k
|
||||||
|
BjICjyWQd7GfkVuYgiYiG/O2hy1u1dIMCAB/Zhx1F1mvRfn/Q4eZk2GSOUM+kC0x
|
||||||
|
aNsn2827VGLOGFywUhRmu7J9QSQ3x1Pi5BME7eNCAKup0CbrMrZSzKAEuYujLY0U
|
||||||
|
YRxUrguMnV60wxJDCYE14YDxn9t0g7wQmzyndupkAMLNJZX5L83RA6vUEuTVYBFc
|
||||||
|
yB0Fu3oBLQ31y5QOZ7WF/QiO5cPicQJI/oyXlHq497BWS/H28kj1H5ZM8+5yhCYD
|
||||||
|
tgj7dERpAgMBAAECggEAAeW+/88cr83aIRtimiKuaXKXyRXsnNRUivAqPnYEsMVJ
|
||||||
|
s24BmdQMN4QF2u2wzJcZLZ7hT45wvVK1nToMV8bqLZ2F1DSyBRB8B6iznHQG5tFr
|
||||||
|
kEKObtrcuddWYQCvckp3OBZP4GTN/+Vs+r0koF5o+whGR+4xKKrgGvs9UPHlytBf
|
||||||
|
0DMzAzWzGPp6qBPw2sUx/fa9r5TqFW+p4SEOZJUqL2/zEZ6KBWbKw5T1e1y2kMEc
|
||||||
|
cquUQ4avqK/N1nwRNKUnTvW827v0k7HQ2cFdrjIATNlICslOWJQicG5GUOuSBkTC
|
||||||
|
0FFkSTtHP4qm0BqShjv6NDmzX+3WCVkGOKFOI+zuWQKBgQDBq8yEcvfMJY98KNlR
|
||||||
|
eKKdJAMJvKdoD65Yv6EG7ZzpeEWHaTGhu71RPgHYkHn8h1T/9WniroSk19+zb4lP
|
||||||
|
mMsBwxpg5HejWPzIiiJRkRCRA7aZZfvaXfIWryB4kI1tlGHBNN/+SYpG1zdNumtp
|
||||||
|
Xyb/sQWMMWRZdRgclF8V+NvduwKBgQDiaM59gBROleREduFZE1a0oXtt+CrwrPlz
|
||||||
|
hclrkYl1FbTA4TdL4JNbj5jCXCR8YakFhxWEmhwq+Dgl1NQY/YjHyG3w2imaeASX
|
||||||
|
QUsEvAIvNrv1mIELiYCLmUElyX4WL3UhqveOFcZUvR1Z4TTwruPQmXf6BJEBLbWI
|
||||||
|
f7odmG6yKwKBgQCzpuLjZiZY9+qe2OGmQopNzE8JJDgCPrGS38fGvnnU1N1iXAFP
|
||||||
|
LvDRwPxDYNnXl84QVR2wygR/SUTYlTlBXdHKw6nfgW89Vlm+yOxGz5MXgeNLbp/u
|
||||||
|
k0DzK+aqECUxJfh8GclCgANF7XP+pVPn/f0WKKalwld86DLCqBuALUX+6wKBgCUh
|
||||||
|
gxvZ8Xqh4nnH9VUicsnU4eU7Ge+2roJfopTdnWlyUd6AEQ2EmyYc+rSFYAZ2Db42
|
||||||
|
VTUWASCa7LpnmREwI0qAeGdToBcRL8+OibsRClqr409331IBDu/WBnUoAmGpDtCi
|
||||||
|
tU68C3bCPRoMcR430GzZfm+maBGFaYwlRmSsJxtZAoGADSA3uAZBuWNDPNKUas2k
|
||||||
|
Z2dXFEPNpViMjQzJ+Ko7lbOBpUUUQfZF2VMSK4lcnhhbmhcMrYzWWmh6uaw78aHY
|
||||||
|
e3M//BfcVMdxHw7EemGOViNNq3uDIwzvYteoe6fAOA7MaV+WjJaf+smceR4o38fk
|
||||||
|
U9RTkKpRJIcvEW5bvTI9h4o=
|
||||||
|
-----END PRIVATE KEY-----
|
98
haproxy-sni/haproxy.cfg
Normal file
98
haproxy-sni/haproxy.cfg
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#####################################
|
||||||
|
## Global Configuration & Defaults ##
|
||||||
|
#####################################
|
||||||
|
|
||||||
|
global
|
||||||
|
log stderr format iso local7
|
||||||
|
|
||||||
|
# generated 2021-06-05, Mozilla Guideline v5.6, HAProxy 2.1, OpenSSL 1.1.1d, intermediate configuration
|
||||||
|
# https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.1d&guideline=5.6
|
||||||
|
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||||
|
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
|
||||||
|
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
|
||||||
|
|
||||||
|
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||||
|
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
|
||||||
|
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
|
||||||
|
|
||||||
|
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
|
||||||
|
ssl-dh-param-file /etc/ssl/dhparam.pem
|
||||||
|
|
||||||
|
defaults
|
||||||
|
log global
|
||||||
|
timeout connect 30000
|
||||||
|
timeout check 300000
|
||||||
|
timeout client 300000
|
||||||
|
timeout server 300000
|
||||||
|
|
||||||
|
############################################################################
|
||||||
|
## Frontends: HTTP; HTTPS → HTTPS SNI-based; HTTPS → HTTP(S) header-based ##
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
frontend http_redirect_frontend
|
||||||
|
# HTTP backend to redirect everything to HTTPS
|
||||||
|
bind :::80 v4v6
|
||||||
|
mode http
|
||||||
|
http-request redirect scheme https
|
||||||
|
|
||||||
|
frontend https_sni_frontend
|
||||||
|
# TCP backend to forward to HTTPS backends based on SNI
|
||||||
|
bind :::443 v4v6
|
||||||
|
mode tcp
|
||||||
|
|
||||||
|
# Wait up to 5s for a SNI header & only accept TLS connections
|
||||||
|
tcp-request inspect-delay 5s
|
||||||
|
tcp-request content capture req.ssl_sni len 255
|
||||||
|
log-format "%ci:%cp -> %[capture.req.hdr(0)] @ %f (%fi:%fp) -> %b (%bi:%bp)"
|
||||||
|
tcp-request content accept if { req.ssl_hello_type 1 }
|
||||||
|
|
||||||
|
###################################################
|
||||||
|
## Rules: forward to HTTPS(S) header-based rules ##
|
||||||
|
###################################################
|
||||||
|
acl use_http_backend req.ssl_sni -i "codeberg.org"
|
||||||
|
acl use_http_backend req.ssl_sni -i "join.codeberg.org"
|
||||||
|
use_backend https_termination_backend if use_http_backend
|
||||||
|
|
||||||
|
############################
|
||||||
|
## Rules: HTTPS SNI-based ##
|
||||||
|
############################
|
||||||
|
# use_backend xyz_backend if { req.ssl_sni -i "xyz" }
|
||||||
|
default_backend pages_backend
|
||||||
|
|
||||||
|
frontend https_termination_frontend
|
||||||
|
# Terminate TLS for HTTP backends
|
||||||
|
bind /tmp/haproxy-tls-termination.sock accept-proxy ssl strict-sni alpn h2,http/1.1 crt /etc/ssl/private/haproxy/
|
||||||
|
mode http
|
||||||
|
|
||||||
|
# HSTS (63072000 seconds)
|
||||||
|
http-response set-header Strict-Transport-Security max-age=63072000
|
||||||
|
|
||||||
|
http-request capture req.hdr(Host) len 255
|
||||||
|
log-format "%ci:%cp -> %[capture.req.hdr(0)] @ %f (%fi:%fp) -> %b (%bi:%bp)"
|
||||||
|
|
||||||
|
##################################
|
||||||
|
## Rules: HTTPS(S) header-based ##
|
||||||
|
##################################
|
||||||
|
use_backend gitea_backend if { hdr(host) -i codeberg.org }
|
||||||
|
|
||||||
|
backend https_termination_backend
|
||||||
|
# Redirect to the terminating HTTPS frontend for all HTTP backends
|
||||||
|
server https_termination_server /tmp/haproxy-tls-termination.sock send-proxy-v2-ssl-cn
|
||||||
|
mode tcp
|
||||||
|
|
||||||
|
###############################
|
||||||
|
## Backends: HTTPS SNI-based ##
|
||||||
|
###############################
|
||||||
|
|
||||||
|
backend pages_backend
|
||||||
|
# Pages server is a HTTP backend that uses its own certificates for custom domains
|
||||||
|
server pages_server pages:443
|
||||||
|
mode tcp
|
||||||
|
|
||||||
|
####################################
|
||||||
|
## Backends: HTTP(S) header-based ##
|
||||||
|
####################################
|
||||||
|
|
||||||
|
backend gitea_backend
|
||||||
|
server gitea_server gitea:80
|
||||||
|
mode http
|
1
haproxy-sni/pages-www/index.html
Normal file
1
haproxy-sni/pages-www/index.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello to Pages!
|
4
haproxy-sni/pages.Caddyfile
Normal file
4
haproxy-sni/pages.Caddyfile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
https://example-page.org
|
||||||
|
|
||||||
|
tls internal
|
||||||
|
file_server
|
22
haproxy-sni/test.sh
Executable file
22
haproxy-sni/test.sh
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/bin/sh
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
exec curl -k --resolve '*:443:127.0.0.1' "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "[FAIL] $@"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Connecting to Gitea..."
|
||||||
|
res=$(curl https://codeberg.org -sk --resolve '*:443:127.0.0.1' --trace-ascii gitea.dump | tee /dev/stderr)
|
||||||
|
echo "$res" | grep -Fx 'Hello to Gitea!' >/dev/null || fail "Gitea didn't answer"
|
||||||
|
grep '^== Info: issuer: O=mkcert development CA;' gitea.dump || { grep grep '^== Info: issuer:' gitea.dump; fail "Gitea didn't use the correct certificate!"; }
|
||||||
|
|
||||||
|
echo "Connecting to Pages..."
|
||||||
|
res=$(curl https://example-page.org -sk --resolve '*:443:127.0.0.1' --trace-ascii pages.dump | tee /dev/stderr)
|
||||||
|
echo "$res" | grep -Fx 'Hello to Pages!' >/dev/null || fail "Pages didn't answer"
|
||||||
|
grep '^== Info: issuer: CN=Caddy Local Authority\b' pages.dump || { grep '^== Info: issuer:' pages.dump; fail "Pages didn't use the correct certificate!"; }
|
||||||
|
|
||||||
|
echo "All tests succeeded"
|
||||||
|
rm *.dump
|
Loading…
Reference in a new issue