浏览代码

Merge pull request #462 from dolonet/contrib/docker-sni-router

Add docker-compose example with HAProxy SNI router
pull/472/head
Sergei Arkhipov 3 周前
父节点
当前提交
5953f9320c
没有帐户链接到提交者的电子邮件

+ 30
- 0
contrib/sni-router/Caddyfile 查看文件

@@ -0,0 +1,30 @@
1
+{
2
+	# Caddy sits behind HAProxy which passes raw TLS through on :8443.
3
+	# ACME HTTP-01 challenges arrive on :80 via HAProxy's acl passthrough.
4
+	http_port 80
5
+	https_port 8443
6
+
7
+	# HAProxy forwards connections to :8443 with a PROXY protocol v2
8
+	# header (see haproxy.cfg `send-proxy-v2`).  The proxy_protocol
9
+	# listener wrapper strips the header and exposes the real client IP
10
+	# to Caddy's access log.  The `tls` wrapper must follow so that TLS
11
+	# is terminated on the unwrapped connection.
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.
16
+	servers :8443 {
17
+		listener_wrappers {
18
+			proxy_protocol {
19
+				timeout 5s
20
+				allow 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
21
+			}
22
+			tls
23
+		}
24
+	}
25
+}
26
+
27
+{$DOMAIN} {
28
+	root * /srv
29
+	file_server
30
+}

+ 89
- 0
contrib/sni-router/README.md 查看文件

@@ -0,0 +1,89 @@
1
+# SNI-routing deployment for mtg
2
+
3
+A turnkey `docker compose` setup that puts an SNI-aware TCP router
4
+(HAProxy) in front of mtg **and** a real web server (Caddy with
5
+automatic HTTPS).
6
+
7
+## Why
8
+
9
+Modern DPI systems actively probe suspected proxies.  If the server
10
+closes the connection or returns something unexpected, the IP gets
11
+flagged.  With this setup:
12
+
13
+- **Telegram clients** connect to port 443, HAProxy sees the configured
14
+  SNI and routes them to mtg (FakeTLS).
15
+- **Everything else** (browsers, DPI probes, scanners) is routed to
16
+  Caddy, which responds with a real Let's Encrypt certificate and serves
17
+  genuine web content.
18
+
19
+Because your domain's DNS points to this server, the SNI/IP match is
20
+natural and passive DPI has nothing to flag.
21
+
22
+## Quick start
23
+
24
+```bash
25
+# 1. Point your domain's DNS A/AAAA record to this server's IP.
26
+
27
+# 2. Generate an mtg secret:
28
+docker run --rm nineseconds/mtg:2 generate-secret --hex YOUR_DOMAIN
29
+
30
+# 3. Edit the config files:
31
+#    - mtg-config.toml  →  paste the secret
32
+#    - haproxy.cfg       →  replace "example.com" in the SNI ACL
33
+#    - .env or export    →  DOMAIN=your.domain
34
+
35
+# 4. (Optional) put your site content into www/
36
+
37
+# 5. Start:
38
+docker compose up -d
39
+
40
+# 6. Verify:
41
+#    - Open https://YOUR_DOMAIN in a browser → you should see the web page
42
+#    - Configure Telegram with the proxy link from:
43
+docker compose exec mtg mtg access /config/config.toml
44
+```
45
+
46
+## Real client IPs (PROXY protocol)
47
+
48
+HAProxy forwards TCP connections to mtg and Caddy with a PROXY protocol
49
+v2 header so both backends see the real client IP instead of HAProxy's
50
+container address.  The three pieces must stay in sync:
51
+
52
+- `haproxy.cfg` — `send-proxy-v2` on the `mtg` and `web` backend `server` lines
53
+- `mtg-config.toml` — `proxy-protocol-listener = true`
54
+- `Caddyfile` — `listener_wrappers { proxy_protocol { ... } tls }` on `:8443`
55
+
56
+If you disable one, disable all three, otherwise the backend will fail
57
+to parse the connection.
58
+
59
+## ACME (Let's Encrypt) notes
60
+
61
+HAProxy passes `/.well-known/acme-challenge/` requests on `:80` to
62
+Caddy so that HTTP-01 validation works out of the box.  Make sure your
63
+domain's DNS A/AAAA record points to this server before starting.
64
+
65
+## Architecture
66
+
67
+```
68
+              ┌──────────────────┐
69
+ :443  ──────>│    HAProxy       │
70
+              │  (TCP, SNI peek) │
71
+              └──┬───────────┬───┘
72
+    SNI match    │           │  default
73
+                 v           v
74
+           ┌─────────┐  ┌─────────┐
75
+           │   mtg    │  │  Caddy  │
76
+           │ :3128    │  │ :8443   │
77
+           │ FakeTLS  │  │ real TLS│
78
+           └─────────┘  └─────────┘
79
+```
80
+
81
+## Files
82
+
83
+| File | Purpose |
84
+|---|---|
85
+| `docker-compose.yml` | Service definitions |
86
+| `haproxy.cfg` | SNI routing rules — **edit the domain** |
87
+| `mtg-config.toml` | mtg proxy config — **paste your secret** |
88
+| `Caddyfile` | Web server config (auto-HTTPS) |
89
+| `www/` | Static site content served by Caddy |

+ 54
- 0
contrib/sni-router/docker-compose.yml 查看文件

@@ -0,0 +1,54 @@
1
+# SNI-routing deployment: HAProxy (443) -> mtg + real web backend
2
+#
3
+# This setup puts an SNI-aware TCP router in front of mtg so that:
4
+#   - Telegram clients (FakeTLS with the correct SNI) are routed to mtg
5
+#   - All other TLS traffic (including DPI probes) reaches the real web
6
+#     server, which responds with a genuine certificate
7
+#
8
+# The result: active probes see a real website; passive DPI sees matching
9
+# SNI/IP because the domain resolves to this server's IP.
10
+#
11
+# Quick start:
12
+#   1. Set YOUR_DOMAIN below (and in mtg-config.toml)
13
+#   2. docker compose up -d
14
+#   3. mtg generate-secret YOUR_DOMAIN   -> put it in mtg-config.toml
15
+#   4. docker compose restart mtg
16
+#
17
+# See BEST_PRACTICES.md and the project wiki for background.
18
+
19
+services:
20
+  haproxy:
21
+    image: haproxy:lts-alpine
22
+    ports:
23
+      - "443:443"
24
+      - "80:80"
25
+    volumes:
26
+      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
27
+    depends_on:
28
+      - mtg
29
+      - web
30
+    restart: unless-stopped
31
+
32
+  mtg:
33
+    image: nineseconds/mtg:2
34
+    volumes:
35
+      - ./mtg-config.toml:/config/config.toml:ro
36
+    expose:
37
+      - "3128"
38
+    restart: unless-stopped
39
+
40
+  web:
41
+    image: caddy:alpine
42
+    volumes:
43
+      - ./Caddyfile:/etc/caddy/Caddyfile:ro
44
+      - caddy_data:/data
45
+      - ./www:/srv:ro
46
+    expose:
47
+      - "80"
48
+      - "8443"
49
+    environment:
50
+      DOMAIN: ${DOMAIN:-example.com}
51
+    restart: unless-stopped
52
+
53
+volumes:
54
+  caddy_data:

+ 62
- 0
contrib/sni-router/haproxy.cfg 查看文件

@@ -0,0 +1,62 @@
1
+# HAProxy SNI router — Layer 4 (TCP mode)
2
+#
3
+# Inspects the SNI in the TLS ClientHello and routes traffic:
4
+#   - SNI matching the mtg secret domain  ->  mtg (FakeTLS / MTProto)
5
+#   - Everything else                      ->  real web backend (Caddy)
6
+#
7
+# Because routing happens before TLS termination, each backend sees the
8
+# raw ClientHello and handles TLS itself.  The real web backend therefore
9
+# presents a genuine certificate to any probe or browser.
10
+
11
+global
12
+    log stdout format raw local0 info
13
+    maxconn 4096
14
+
15
+defaults
16
+    log     global
17
+    mode    tcp
18
+    option  tcplog
19
+    timeout connect 5s
20
+    timeout client  60s
21
+    timeout server  60s
22
+
23
+# --- HTTP :80 — ACME challenges + redirect -----------------------------------
24
+
25
+frontend http
26
+    bind *:80
27
+    mode http
28
+
29
+    # Let Caddy answer ACME HTTP-01 challenges for Let's Encrypt.
30
+    acl is_acme path_beg /.well-known/acme-challenge/
31
+    use_backend web_acme if is_acme
32
+
33
+    http-request redirect scheme https code 301
34
+
35
+# --- TLS :443 — SNI-based routing -------------------------------------------
36
+
37
+frontend tls
38
+    bind *:443
39
+    tcp-request inspect-delay 5s
40
+    tcp-request content accept if { req_ssl_hello_type 1 }
41
+
42
+    # Route Telegram clients to mtg.
43
+    # Replace "example.com" with the domain from your mtg secret.
44
+    use_backend mtg if { req_ssl_sni -i example.com }
45
+
46
+    default_backend web
47
+
48
+backend mtg
49
+    # send-proxy-v2 prepends a PROXY protocol v2 header so mtg sees the
50
+    # real client IP instead of HAProxy's.  mtg must have
51
+    # `proxy-protocol-listener = true` in its config.
52
+    server mtg mtg:3128 send-proxy-v2
53
+
54
+backend web
55
+    # send-proxy-v2 prepends a PROXY protocol v2 header so Caddy logs the
56
+    # real client IP instead of HAProxy's.  Caddy must enable the
57
+    # proxy_protocol listener wrapper on :8443 (see Caddyfile).
58
+    server web web:8443 send-proxy-v2
59
+
60
+backend web_acme
61
+    mode http
62
+    server web web:80

+ 17
- 0
contrib/sni-router/mtg-config.toml 查看文件

@@ -0,0 +1,17 @@
1
+# Minimal mtg configuration for the SNI-router setup.
2
+#
3
+# 1. Generate a secret:  mtg generate-secret --hex example.com
4
+# 2. Paste it below.
5
+# 3. Replace example.com with your actual domain everywhere.
6
+
7
+secret = "PASTE_YOUR_SECRET_HERE"
8
+bind-to = "0.0.0.0:3128"
9
+
10
+# HAProxy in front sends PROXY protocol v2 headers so mtg can see the
11
+# real client IP.  Keep this in sync with haproxy.cfg (`send-proxy-v2`).
12
+proxy-protocol-listener = true
13
+
14
+[defense.anti-replay]
15
+enabled = true
16
+max-size = "1mib"
17
+error-rate = 0.001

+ 5
- 0
contrib/sni-router/www/index.html 查看文件

@@ -0,0 +1,5 @@
1
+<!doctype html>
2
+<html lang="en">
3
+<head><meta charset="utf-8"><title>Welcome</title></head>
4
+<body><h1>It works!</h1><p>Replace this with your own content.</p></body>
5
+</html>

正在加载...
取消
保存