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