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

Add docker-compose example with HAProxy SNI router

Turnkey deployment: HAProxy on :443 peeks at the TLS SNI and routes
Telegram clients to mtg while forwarding everything else (including DPI
probes) to a real Caddy web server with automatic HTTPS.

This is the setup recommended in BEST_PRACTICES.md, packaged so that
operators can clone and run it with minimal configuration.

Refs: #458
pull/462/head
dolonet пре 3 недеља
родитељ
комит
0c1d001949

+ 19
- 0
contrib/sni-router/Caddyfile Прегледај датотеку

@@ -0,0 +1,19 @@
1
+{
2
+	# Caddy listens on 8443 behind HAProxy, which passes raw TLS through.
3
+	# Caddy terminates TLS itself and auto-obtains a Let's Encrypt certificate.
4
+	#
5
+	# If your domain's DNS already points to this server, ACME HTTP-01 challenge
6
+	# works through the HAProxy http frontend (:80 → redirect).  For DNS-01
7
+	# or other ACME methods, see https://caddyserver.com/docs/automatic-https
8
+}
9
+
10
+{$DOMAIN}:8443 {
11
+	tls {
12
+		# Use the ACME HTTP-01 challenge on port 80.
13
+		# HAProxy forwards :80 as HTTP, so Caddy can answer the challenge
14
+		# if you add an acl exception in haproxy.cfg (see README), or use
15
+		# DNS-01 instead.
16
+	}
17
+	root * /srv
18
+	file_server
19
+}

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

@@ -0,0 +1,83 @@
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
+## ACME (Let's Encrypt) notes
47
+
48
+Caddy needs to answer the ACME HTTP-01 challenge on port 80.  The
49
+default `haproxy.cfg` redirects all `:80` traffic to HTTPS.  If Caddy
50
+cannot obtain a certificate, either:
51
+
52
+1. Temporarily stop HAProxy, let Caddy bind `:80` directly for the
53
+   initial certificate, then start the full stack; or
54
+2. Use DNS-01 validation in the Caddyfile (requires a DNS provider
55
+   plugin); or
56
+3. Add an HAProxy ACL that passes `/.well-known/acme-challenge/`
57
+   requests to the Caddy backend instead of redirecting.
58
+
59
+## Architecture
60
+
61
+```
62
+              ┌──────────────────┐
63
+ :443  ──────>│    HAProxy       │
64
+              │  (TCP, SNI peek) │
65
+              └──┬───────────┬───┘
66
+    SNI match    │           │  default
67
+                 v           v
68
+           ┌─────────┐  ┌─────────┐
69
+           │   mtg    │  │  Caddy  │
70
+           │ :3128    │  │ :8443   │
71
+           │ FakeTLS  │  │ real TLS│
72
+           └─────────┘  └─────────┘
73
+```
74
+
75
+## Files
76
+
77
+| File | Purpose |
78
+|---|---|
79
+| `docker-compose.yml` | Service definitions |
80
+| `haproxy.cfg` | SNI routing rules — **edit the domain** |
81
+| `mtg-config.toml` | mtg proxy config — **paste your secret** |
82
+| `Caddyfile` | Web server config (auto-HTTPS) |
83
+| `www/` | Static site content served by Caddy |

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

@@ -0,0 +1,53 @@
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
+      - "8443"
48
+    environment:
49
+      DOMAIN: ${DOMAIN:-example.com}
50
+    restart: unless-stopped
51
+
52
+volumes:
53
+  caddy_data:

+ 47
- 0
contrib/sni-router/haproxy.cfg Прегледај датотеку

@@ -0,0 +1,47 @@
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 — redirect to HTTPS -------------------------------------------
24
+
25
+frontend http
26
+    bind *:80
27
+    mode http
28
+    http-request redirect scheme https code 301
29
+
30
+# --- TLS :443 — SNI-based routing -------------------------------------------
31
+
32
+frontend tls
33
+    bind *:443
34
+    tcp-request inspect-delay 5s
35
+    tcp-request content accept if { req_ssl_hello_type 1 }
36
+
37
+    # Route Telegram clients to mtg.
38
+    # Replace "example.com" with the domain from your mtg secret.
39
+    use_backend mtg if { req_ssl_sni -i example.com }
40
+
41
+    default_backend web
42
+
43
+backend mtg
44
+    server mtg mtg:3128
45
+
46
+backend web
47
+    server web web:8443

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

@@ -0,0 +1,13 @@
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
+[defense.anti-replay]
11
+enabled = true
12
+max-size = "1mib"
13
+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>

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