Sfoglia il codice sorgente

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 2 mesi fa
parent
commit
2d7c71657c
Nessun account collegato all'indirizzo email del committer

+ 6
- 4
contrib/sni-router/Caddyfile Vedi File

10
 	# to Caddy's access log.  The `tls` wrapper must follow so that TLS
10
 	# to Caddy's access log.  The `tls` wrapper must follow so that TLS
11
 	# is terminated on the unwrapped connection.
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
 	servers :8443 {
18
 	servers :8443 {
17
 		listener_wrappers {
19
 		listener_wrappers {
18
 			proxy_protocol {
20
 			proxy_protocol {
19
 				timeout 5s
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
 			tls
24
 			tls
23
 		}
25
 		}

+ 23
- 0
contrib/sni-router/README.md Vedi File

63
 If you disable one, disable all four, otherwise the backend will fail
63
 If you disable one, disable all four, otherwise the backend will fail
64
 to parse the connection.
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
 ## Fronting loop (why `[domain-fronting]` is set explicitly)
89
 ## Fronting loop (why `[domain-fronting]` is set explicitly)
67
 
90
 
68
 When mtg sees TLS that isn't valid Telegram (a probe or a browser
91
 When mtg sees TLS that isn't valid Telegram (a probe or a browser

+ 13
- 10
contrib/sni-router/docker-compose.yml Vedi File

27
 services:
27
 services:
28
   haproxy:
28
   haproxy:
29
     image: haproxy:lts-alpine
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
     volumes:
34
     volumes:
34
       - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro,Z
35
       - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro,Z
35
     environment:
36
     environment:
38
       - mtg
39
       - mtg
39
       - web
40
       - web
40
     restart: unless-stopped
41
     restart: unless-stopped
41
-    sysctls:
42
-      - net.ipv4.ip_unprivileged_port_start=80
43
 
42
 
44
   mtg:
43
   mtg:
45
     # FIXME: :master until #480 lands in a tagged release; switch back to :2/:3 after release
44
     # FIXME: :master until #480 lands in a tagged release; switch back to :2/:3 after release
46
     image: nineseconds/mtg:master
45
     image: nineseconds/mtg:master
47
     volumes:
46
     volumes:
48
       - ./mtg-config.toml:/config/config.toml:ro,Z
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
     restart: unless-stopped
52
     restart: unless-stopped
52
     extra_hosts:
53
     extra_hosts:
53
       - "host.containers.internal:host-gateway"
54
       - "host.containers.internal:host-gateway"
58
       - ./Caddyfile:/etc/caddy/Caddyfile:ro,Z
59
       - ./Caddyfile:/etc/caddy/Caddyfile:ro,Z
59
       - caddy_data:/data
60
       - caddy_data:/data
60
       - ./www:/srv:ro,Z
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
     environment:
67
     environment:
65
       <<: *domain-env
68
       <<: *domain-env
66
     restart: unless-stopped
69
     restart: unless-stopped

+ 12
- 5
contrib/sni-router/haproxy.cfg Vedi File

23
 # --- HTTP :80 — ACME challenges + redirect -----------------------------------
23
 # --- HTTP :80 — ACME challenges + redirect -----------------------------------
24
 
24
 
25
 frontend http
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
     mode http
29
     mode http
28
 
30
 
29
     # Let Caddy answer ACME HTTP-01 challenges for Let's Encrypt.
31
     # Let Caddy answer ACME HTTP-01 challenges for Let's Encrypt.
35
 # --- TLS :443 — SNI-based routing -------------------------------------------
37
 # --- TLS :443 — SNI-based routing -------------------------------------------
36
 
38
 
37
 frontend tls
39
 frontend tls
38
-    bind *:443
40
+    bind :443,[::]:443
39
     tcp-request inspect-delay 5s
41
     tcp-request inspect-delay 5s
40
     tcp-request content accept if { req_ssl_hello_type 1 }
42
     tcp-request content accept if { req_ssl_hello_type 1 }
41
 
43
 
46
 
48
 
47
     default_backend web
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
 backend mtg
56
 backend mtg
50
     # send-proxy-v2 prepends a PROXY protocol v2 header so mtg sees the
57
     # send-proxy-v2 prepends a PROXY protocol v2 header so mtg sees the
51
     # real client IP instead of HAProxy's.  mtg must have
58
     # real client IP instead of HAProxy's.  mtg must have
52
     # `proxy-protocol-listener = true` in its config.
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
 backend web
62
 backend web
56
     # send-proxy-v2 prepends a PROXY protocol v2 header so Caddy logs the
63
     # send-proxy-v2 prepends a PROXY protocol v2 header so Caddy logs the
57
     # real client IP instead of HAProxy's.  Caddy must enable the
64
     # real client IP instead of HAProxy's.  Caddy must enable the
58
     # proxy_protocol listener wrapper on :8443 (see Caddyfile).
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
 backend web_acme
68
 backend web_acme
62
     mode http
69
     mode http
63
-    server web web:80
70
+    server web 127.0.0.1:8080

Loading…
Annulla
Salva