Alexey Dolotov 1 dzień temu
rodzic
commit
dc7d620a66
No account linked to committer's email address

+ 44
- 0
contrib/sni-router/README.md Wyświetl plik

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
+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
 ## ACME (Let's Encrypt) notes
103
 ## ACME (Let's Encrypt) notes
60
 
104
 
61
 HAProxy passes `/.well-known/acme-challenge/` requests on `:80` to
105
 HAProxy passes `/.well-known/acme-challenge/` requests on `:80` to

+ 17
- 0
contrib/sni-router/docker-compose.yml Wyświetl plik

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 Wyświetl plik

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"

Ładowanie…
Anuluj
Zapisz