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

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 неделю назад
Родитель
Сommit
f0f3611a37
3 измененных файлов: 59 добавлений и 0 удалений
  1. 29
    0
      contrib/sni-router/README.md
  2. 17
    0
      contrib/sni-router/docker-compose.yml
  3. 13
    0
      contrib/sni-router/mtg-config.toml

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

56
 If you disable one, disable all three, otherwise the backend will fail
56
 If you disable one, disable all three, otherwise the backend will fail
57
 to parse the connection.
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
 ## ACME (Let's Encrypt) notes
88
 ## ACME (Let's Encrypt) notes
60
 
89
 
61
 HAProxy passes `/.well-known/acme-challenge/` requests on `:80` to
90
 HAProxy passes `/.well-known/acme-challenge/` requests on `:80` to

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

30
     restart: unless-stopped
30
     restart: unless-stopped
31
     sysctls:
31
     sysctls:
32
       - net.ipv4.ip_unprivileged_port_start=80
32
       - net.ipv4.ip_unprivileged_port_start=80
33
+    networks:
34
+      sni:
33
 
35
 
34
   mtg:
36
   mtg:
35
     image: nineseconds/mtg:2
37
     image: nineseconds/mtg:2
40
     restart: unless-stopped
42
     restart: unless-stopped
41
     extra_hosts:
43
     extra_hosts:
42
       - "host.containers.internal:host-gateway"
44
       - "host.containers.internal:host-gateway"
45
+    networks:
46
+      sni:
43
 
47
 
44
   web:
48
   web:
45
     image: caddy:alpine
49
     image: caddy:alpine
53
     environment:
57
     environment:
54
       DOMAIN: ${DOMAIN:-example.com}
58
       DOMAIN: ${DOMAIN:-example.com}
55
     restart: unless-stopped
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
 volumes:
67
 volumes:
58
   caddy_data:
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
 # real client IP.  Keep this in sync with haproxy.cfg (`send-proxy-v2`).
11
 # real client IP.  Keep this in sync with haproxy.cfg (`send-proxy-v2`).
12
 proxy-protocol-listener = true
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
 [defense.anti-replay]
27
 [defense.anti-replay]
15
 enabled = true
28
 enabled = true
16
 max-size = "1mib"
29
 max-size = "1mib"

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