Browse Source

Merge bf501a80f7 into 8d143d7bde

pull/478/merge
Alexey Dolotov 1 day ago
parent
commit
dc7d620a66
No account linked to committer's email address

+ 44
- 0
contrib/sni-router/README.md View File

@@ -56,6 +56,50 @@ 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
+The trigger is DNS, not name equality: the loop reproduces whenever
71
+the secret's hostname resolves to this host, regardless of how it
72
+relates to the domain Caddy serves (same name, subdomain, parent, or
73
+unrelated).  In an SNI-router deployment the secret's hostname has to
74
+point here for clients to reach mtg in the first place, so the loop
75
+is the default state unless mtg is steered away from HAProxy.
76
+
77
+To break the loop, `mtg-config.toml` pins the fronting target to
78
+Caddy's container address directly:
79
+
80
+```toml
81
+[domain-fronting]
82
+ip = "172.28.0.10"
83
+port = 8443
84
+proxy-protocol = true
85
+```
86
+
87
+The IP matches `services.web.networks.sni.ipv4_address` in
88
+`docker-compose.yml` (mtg's `domain-fronting.ip` only accepts a literal
89
+IP, not a hostname, hence the static `sni` network).  `proxy-protocol =
90
+true` matches Caddy's `:8443` listener wrapper so the real client IP
91
+still propagates to Caddy's logs.
92
+
93
+If you change Caddy's pinned IP, update both files together.  If
94
+`172.28.0.0/24` collides with another network on this host, change the
95
+subnet in `docker-compose.yml` and the IP in `mtg-config.toml` to match.
96
+
97
+> Caveat for IPv6 clients: when an IPv6 probe is fronted, mtg's
98
+> outbound PROXY v2 header has an IPv6 source but an IPv4 destination
99
+> (Caddy's pinned address).  Caddy may refuse the mixed-family header
100
+> and log the docker-network address instead of the real client IP for
101
+> that connection.  Telegram traffic is unaffected.
102
+
59 103
 ## ACME (Let's Encrypt) notes
60 104
 
61 105
 HAProxy passes `/.well-known/acme-challenge/` requests on `:80` to

+ 17
- 0
contrib/sni-router/docker-compose.yml View File

@@ -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 View File

@@ -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…
Cancel
Save