Просмотр исходного кода

Merge pull request #522 from 9seconds/sni-router-host-mode-real-ips

sni-router: host-net HAProxy to preserve real client IPs
pull/540/head
Sergei Arkhipov 11 часов назад
Родитель
Сommit
2d7c71657c
Аккаунт пользователя с таким Email не найден

+ 6
- 4
contrib/sni-router/Caddyfile Просмотреть файл

@@ -10,14 +10,16 @@
10 10
 	# to Caddy's access log.  The `tls` wrapper must follow so that TLS
11 11
 	# is terminated on the unwrapped connection.
12 12
 	#
13
-	# `allow` lists the networks permitted to send PROXY headers.  These
14
-	# ranges cover docker compose's default bridge networks; tighten
15
-	# them if you pin a specific subnet in docker-compose.yml.
13
+	# `allow` lists the networks permitted to send PROXY headers.
14
+	# 127.0.0.1/32 covers HAProxy reaching Caddy over host loopback (HAProxy
15
+	# runs in network_mode: host and connects to the published 127.0.0.1
16
+	# port).  The RFC1918 ranges cover mtg → Caddy on the compose bridge
17
+	# (fronting path; see "Fronting loop" in README.md).
16 18
 	servers :8443 {
17 19
 		listener_wrappers {
18 20
 			proxy_protocol {
19 21
 				timeout 5s
20
-				allow 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
22
+				allow 127.0.0.1/32 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
21 23
 			}
22 24
 			tls
23 25
 		}

+ 23
- 0
contrib/sni-router/README.md Просмотреть файл

@@ -63,6 +63,29 @@ must stay in sync:
63 63
 If you disable one, disable all four, otherwise the backend will fail
64 64
 to parse the connection.
65 65
 
66
+### Why HAProxy uses `network_mode: host`
67
+
68
+A published port on a bridge network rewrites the source IP of inbound
69
+connections to the bridge gateway before HAProxy sees it (Docker's
70
+`docker-proxy`, Podman's `slirp4netns`/`pasta`), so the PROXY v2 header
71
+HAProxy forwards downstream carries that gateway address, not the real
72
+client.  Host-mode HAProxy binds in the host netns directly, no NAT in
73
+the path, and the rewrite never happens.  mtg and Caddy stay on the
74
+compose bridge and are published on `127.0.0.1` only — HAProxy reaches
75
+them over host loopback.  `mtg-config.toml` does not need to change;
76
+fronting still uses `host = "web"` over compose-network DNS.
77
+
78
+**Trade-offs.**
79
+- HAProxy owns the host's `:443` and `:80` — don't run anything else
80
+  on those ports.
81
+- Linux host only.  On Docker Desktop (macOS/Windows), "host" means
82
+  the Linux VM, not the user's machine, so external clients can't
83
+  reach the proxy.
84
+- If you run Docker with `userns-remap`, the in-container "root"
85
+  loses the privilege to bind `<1024` on the host; either disable
86
+  `userns-remap` for this stack or lower `net.ipv4.ip_unprivileged_port_start`
87
+  on the host.
88
+
66 89
 ## Fronting loop (why `[domain-fronting]` is set explicitly)
67 90
 
68 91
 When mtg sees TLS that isn't valid Telegram (a probe or a browser

+ 13
- 10
contrib/sni-router/docker-compose.yml Просмотреть файл

@@ -27,9 +27,10 @@ x-domain-env: &domain-env
27 27
 services:
28 28
   haproxy:
29 29
     image: haproxy:lts-alpine
30
-    ports:
31
-      - "443:443"
32
-      - "80:80"
30
+    # Host netns so HAProxy sees real client IPs (v4/v6) instead of the
31
+    # bridge gateway address.  Linux host only; see README → "Why HAProxy
32
+    # uses network_mode: host" for the rationale and trade-off.
33
+    network_mode: host
33 34
     volumes:
34 35
       - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro,Z
35 36
     environment:
@@ -38,16 +39,16 @@ services:
38 39
       - mtg
39 40
       - web
40 41
     restart: unless-stopped
41
-    sysctls:
42
-      - net.ipv4.ip_unprivileged_port_start=80
43 42
 
44 43
   mtg:
45 44
     # FIXME: :master until #480 lands in a tagged release; switch back to :2/:3 after release
46 45
     image: nineseconds/mtg:master
47 46
     volumes:
48 47
       - ./mtg-config.toml:/config/config.toml:ro,Z
49
-    expose:
50
-      - "3128"
48
+    # Published on host loopback only — HAProxy (host netns) reaches it via
49
+    # 127.0.0.1.
50
+    ports:
51
+      - "127.0.0.1:3128:3128"
51 52
     restart: unless-stopped
52 53
     extra_hosts:
53 54
       - "host.containers.internal:host-gateway"
@@ -58,9 +59,11 @@ services:
58 59
       - ./Caddyfile:/etc/caddy/Caddyfile:ro,Z
59 60
       - caddy_data:/data
60 61
       - ./www:/srv:ro,Z
61
-    expose:
62
-      - "80"
63
-      - "8443"
62
+    # Published on host loopback only — HAProxy reaches Caddy on 127.0.0.1.
63
+    # Port 8080 (not 80) on the host because HAProxy already owns host :80.
64
+    ports:
65
+      - "127.0.0.1:8080:80"
66
+      - "127.0.0.1:8443:8443"
64 67
     environment:
65 68
       <<: *domain-env
66 69
     restart: unless-stopped

+ 12
- 5
contrib/sni-router/haproxy.cfg Просмотреть файл

@@ -23,7 +23,9 @@ defaults
23 23
 # --- HTTP :80 — ACME challenges + redirect -----------------------------------
24 24
 
25 25
 frontend http
26
-    bind *:80
26
+    # Explicit v4 + v6 binds so IPv6 clients are accepted regardless of
27
+    # the host's net.ipv6.bindv6only sysctl.
28
+    bind :80,[::]:80
27 29
     mode http
28 30
 
29 31
     # Let Caddy answer ACME HTTP-01 challenges for Let's Encrypt.
@@ -35,7 +37,7 @@ frontend http
35 37
 # --- TLS :443 — SNI-based routing -------------------------------------------
36 38
 
37 39
 frontend tls
38
-    bind *:443
40
+    bind :443,[::]:443
39 41
     tcp-request inspect-delay 5s
40 42
     tcp-request content accept if { req_ssl_hello_type 1 }
41 43
 
@@ -46,18 +48,23 @@ frontend tls
46 48
 
47 49
     default_backend web
48 50
 
51
+# Backends reach mtg and web on host loopback — they publish to 127.0.0.1
52
+# (see docker-compose.yml), and HAProxy runs in the host netns
53
+# (network_mode: host).  PROXY v2 still carries the real client address
54
+# (v4 or v6) end-to-end, independent of the loopback transport.
55
+
49 56
 backend mtg
50 57
     # send-proxy-v2 prepends a PROXY protocol v2 header so mtg sees the
51 58
     # real client IP instead of HAProxy's.  mtg must have
52 59
     # `proxy-protocol-listener = true` in its config.
53
-    server mtg mtg:3128 send-proxy-v2
60
+    server mtg 127.0.0.1:3128 send-proxy-v2
54 61
 
55 62
 backend web
56 63
     # send-proxy-v2 prepends a PROXY protocol v2 header so Caddy logs the
57 64
     # real client IP instead of HAProxy's.  Caddy must enable the
58 65
     # proxy_protocol listener wrapper on :8443 (see Caddyfile).
59
-    server web web:8443 send-proxy-v2
66
+    server web 127.0.0.1:8443 send-proxy-v2
60 67
 
61 68
 backend web_acme
62 69
     mode http
63
-    server web web:80
70
+    server web 127.0.0.1:8080

Загрузка…
Отмена
Сохранить