Преглед изворни кода

sni-router: break domain-fronting loop with pinned Caddy IP

When the secret's domain points at this server (the recommended
deployment), mtg's default fronting behavior dials that domain on :443
and the connection lands on HAProxy. HAProxy sees the SNI matching the
secret and routes back to mtg, looping until something gives.

Pin Caddy's container address via a static `sni` network and point
mtg's `[domain-fronting]` at it directly with `proxy-protocol = true`,
matching Caddy's :8443 PROXY listener wrapper. mtg's
`domain-fronting.ip` only accepts a literal IP (not a hostname), so the
network needs a fixed subnet.

README documents the loop, the fix, and the requirement to keep the
pinned IP in sync between docker-compose.yml and mtg-config.toml.

Reported by @gaudima in #462.
pull/478/head
Alexey пре 1 недеља
родитељ
комит
f0f3611a37

+ 29
- 0
contrib/sni-router/README.md Прегледај датотеку

@@ -56,6 +56,35 @@ container address.  The three pieces must stay in sync:
56 56
 If you disable one, disable all three, otherwise the backend will fail
57 57
 to parse the connection.
58 58
 
59
+## Fronting loop (why `[domain-fronting]` is set explicitly)
60
+
61
+When mtg sees TLS traffic that isn't valid Telegram (a probe or a
62
+browser hitting your domain on `:443`), it forwards that connection to
63
+a real web server — "domain fronting".  By default mtg uses the
64
+secret's hostname as the fronting target and resolves it via DNS.
65
+
66
+In this setup that hostname resolves back to **this** server, so mtg's
67
+fronting dial would hit HAProxy on `:443`, HAProxy would see the SNI
68
+matching the secret and route the connection back to mtg → loop.
69
+
70
+To break the loop, `mtg-config.toml` pins the fronting target to
71
+Caddy's container address directly:
72
+
73
+```toml
74
+[domain-fronting]
75
+ip = "172.28.0.10"
76
+port = 8443
77
+proxy-protocol = true
78
+```
79
+
80
+The IP matches `services.web.networks.sni.ipv4_address` in
81
+`docker-compose.yml` (mtg's `domain-fronting.ip` only accepts a literal
82
+IP, not a hostname, hence the static `sni` network).  `proxy-protocol =
83
+true` matches Caddy's `:8443` listener wrapper so the real client IP
84
+still propagates to Caddy's logs.
85
+
86
+If you change Caddy's pinned IP, update both files together.
87
+
59 88
 ## ACME (Let's Encrypt) notes
60 89
 
61 90
 HAProxy passes `/.well-known/acme-challenge/` requests on `:80` to

+ 17
- 0
contrib/sni-router/docker-compose.yml Прегледај датотеку

@@ -30,6 +30,8 @@ services:
30 30
     restart: unless-stopped
31 31
     sysctls:
32 32
       - net.ipv4.ip_unprivileged_port_start=80
33
+    networks:
34
+      sni:
33 35
 
34 36
   mtg:
35 37
     image: nineseconds/mtg:2
@@ -40,6 +42,8 @@ services:
40 42
     restart: unless-stopped
41 43
     extra_hosts:
42 44
       - "host.containers.internal:host-gateway"
45
+    networks:
46
+      sni:
43 47
 
44 48
   web:
45 49
     image: caddy:alpine
@@ -53,6 +57,19 @@ services:
53 57
     environment:
54 58
       DOMAIN: ${DOMAIN:-example.com}
55 59
     restart: unless-stopped
60
+    networks:
61
+      sni:
62
+        # Pinned IP so mtg's `domain-fronting.ip` (which only accepts a
63
+        # literal IP, not a hostname) can target Caddy directly and
64
+        # bypass HAProxy.  See README "Fronting loop" section.
65
+        ipv4_address: 172.28.0.10
56 66
 
57 67
 volumes:
58 68
   caddy_data:
69
+
70
+networks:
71
+  sni:
72
+    driver: bridge
73
+    ipam:
74
+      config:
75
+        - subnet: 172.28.0.0/24

+ 13
- 0
contrib/sni-router/mtg-config.toml Прегледај датотеку

@@ -11,6 +11,19 @@ bind-to = "0.0.0.0:3128"
11 11
 # real client IP.  Keep this in sync with haproxy.cfg (`send-proxy-v2`).
12 12
 proxy-protocol-listener = true
13 13
 
14
+# Domain-fronting target.  Without an explicit IP here, mtg resolves the
15
+# secret's hostname via DNS, which points back to this server -> lands
16
+# on HAProxy -> SNI matches the secret -> routed back to mtg -> loop.
17
+#
18
+# The IP below pins Caddy's container address (see docker-compose.yml
19
+# `networks.sni.ipv4_address`) so mtg dials Caddy directly, bypassing
20
+# HAProxy.  `proxy-protocol = true` matches Caddy's :8443 listener
21
+# wrapper so the real client IP propagates end-to-end.
22
+[domain-fronting]
23
+ip = "172.28.0.10"
24
+port = 8443
25
+proxy-protocol = true
26
+
14 27
 [defense.anti-replay]
15 28
 enabled = true
16 29
 max-size = "1mib"

Loading…
Откажи
Сачувај