From fb7ad7ec4043dd3a60d199f3932f8a8047463aad Mon Sep 17 00:00:00 2001 From: "E. Kaparulin" Date: Fri, 26 Jun 2026 23:16:30 +0300 Subject: [PATCH] chore: bump version to 0.1.0-beta.7 --- docs/stealth-setup.md | 237 +++++++++++------------------------------- 1 file changed, 58 insertions(+), 179 deletions(-) diff --git a/docs/stealth-setup.md b/docs/stealth-setup.md index 3645596..4b1fafa 100644 --- a/docs/stealth-setup.md +++ b/docs/stealth-setup.md @@ -1,183 +1,25 @@ -# Stealth Mode: Real TLS Bypass via HAProxy +# Stealth Mode Setup -Stealth mode makes VPN traffic indistinguishable from normal HTTPS by wrapping it in -real TLS with a valid certificate. Deep packet inspection sees a standard TLS 1.3 -connection to a legitimate domain — not a VPN. +Stealth mode makes VPN traffic look like normal HTTPS to deep packet inspection systems. +The client opens a real TLS 1.3 connection, performs a WebSocket upgrade, and then carries +the konduit protocol inside WebSocket binary frames — identical in appearance to any +browser-based web app. -![Stealth mode architecture](diagrams/stealth-architecture.svg) - -## How It Works - -1. **Client** opens a TLS 1.3 connection to port 443 using the system's trusted - certificate store — the same TLS stack as any browser. -2. **HAProxy** terminates TLS using a real certbot certificate. It then inspects the - first byte of the decrypted payload to route traffic: - - HTTP method bytes (`GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `OPTIONS`) → web backend - - Any other byte → konduit VPN backend -3. **Konduit** receives a plain TCP connection and runs its normal handshake. - Stealth mode must be **disabled** on the server side — HAProxy already handled TLS. - -There are two client-side flags: - -- **`tls = true`** — activates real TLS wrapping only (for the HAProxy TLS-termination model above) -- **`[stealth] enabled = true`** — activates TLS **and** WebSocket camouflage (see [WebSocket mode](#websocket-camouflage-tspu--russia-bypass) below) - -Use `tls = true` when HAProxy terminates TLS for you. Use `stealth = true` when the server handles TLS directly and you need WebSocket framing to bypass DPI. +``` +Client ──TLS 1.3──▶ HAProxy :443 (TCP passthrough) ──▶ Konduit :8443 + HTTP/1.1 GET /ws → 101 Switching Protocols + WebSocket binary frames (konduit protocol inside) +``` ## Server Requirements - A domain with a valid TLS certificate (Let's Encrypt / certbot) - HAProxy 2.4+ - Konduit server on a non-public port (e.g. `8443`) -- Web server on a local port (e.g. `8080`) — for non-VPN HTTP requests ## HAProxy Configuration -Combine the certificate and private key into a single PEM file for HAProxy: - -```bash -cat /etc/letsencrypt/live/your-domain.com/fullchain.pem \ - /etc/letsencrypt/live/your-domain.com/privkey.pem \ - > /etc/haproxy/ssl/your-domain.pem -chmod 600 /etc/haproxy/ssl/your-domain.pem -``` - -```haproxy -frontend https-ingress - bind *:443 ssl crt /etc/haproxy/ssl/your-domain.pem - mode tcp - option tcplog - tcp-request inspect-delay 3s - - # Accept as soon as the first byte arrives — VPN connections never send a FIN, - # so WAIT_END would stall every connection for the full inspect-delay. - tcp-request content accept if { req.payload(0,1) -m found } - - use_backend web if { req.payload(0,3) -m str GET } - use_backend web if { req.payload(0,4) -m str POST } - use_backend web if { req.payload(0,4) -m str HEAD } - use_backend web if { req.payload(0,3) -m str PUT } - use_backend web if { req.payload(0,6) -m str DELETE } - use_backend web if { req.payload(0,7) -m str OPTIONS } - - default_backend konduit-vpn - -backend konduit-vpn - mode tcp - server konduit 127.0.0.1:8443 - -backend web - mode http - server web 127.0.0.1:8080 -``` - -Reload after changes: - -```bash -haproxy -c -f /etc/haproxy/haproxy.cfg # validate first -systemctl reload haproxy -``` - -## Konduit Server (`server.toml`) - -Disable stealth on the server — HAProxy terminated TLS before the connection arrives: - -```toml -[server] -listen_addr = "0.0.0.0" -listen_port = 8443 -public_addr = "your-domain.com" -public_port = 443 - -[stealth] -enabled = false -``` - -Bootstrap peers with `--public-port` so generated client configs reference port 443: - -```bash -echo "your-mantra" | ./konduit-ctl bootstrap \ - -l your-domain.com:8443 --public-port 443 -p - -``` - -## Client Configuration (`client.toml`) - -For the HAProxy TLS-termination model (HAProxy decrypts TLS, konduit receives plain TCP): - -```toml -[client] -server_endpoint = "your-domain.com:443" -tls = true -peer_id = "..." -identity_key = "..." -server_public_key = "..." -``` - -For WebSocket camouflage mode (konduit handles TLS and WebSocket end-to-end): - -```toml -[client] -server_endpoint = "your-domain.com:443" -peer_id = "..." -identity_key = "..." -server_public_key = "..." - -[stealth] -enabled = true -``` - -## Flutter / Mobile App - -Enable **Stealth Mode** in the app settings. The toggle maps to `stealth = true` in -the connection config and activates TLS + WebSocket camouflage on all platforms -(Linux, Android, Windows). - -For QR-code-based provisioning, the `t: true` field in the QR payload enables stealth. - -## Verify - -```bash -# A browser must see your real website — not a TLS error or empty response -curl -s https://your-domain.com/ | head -5 - -# Connect the VPN client — should stay connected without 20-second drops -./konduit --config client.toml - -# HAProxy serves both roles: check access log -journalctl -u haproxy -f -``` - -## WebSocket Camouflage (TSPU / Russia Bypass) {#websocket-camouflage-tspu--russia-bypass} - -Some ISPs (notably Russia's TSPU system) allow real TLS but still drop connections whose -payload doesn't look like HTTP. WebSocket camouflage solves this: konduit protocol runs -inside WebSocket binary frames over HTTPS, which TSPU whitelists (Telegram Web, online -games, and countless other services use the same pattern). - -``` -Client ──TLS 1.3──▶ HAProxy :443 (TCP passthrough) ──▶ Konduit :8443 - HTTP/1.1 GET /ws - 101 Switching Protocols - WS binary frames (konduit protocol inside) -``` - -HAProxy acts as a pure TCP proxy — it does **not** terminate TLS in this mode. - -### Server (`server.toml`) - -```toml -[server] -listen_addr = "0.0.0.0" -listen_port = 8443 -public_addr = "your-domain.com" -public_port = 443 -tls = true -tls_cert = "/etc/letsencrypt/live/your-domain.com/fullchain.pem" -tls_key = "/etc/letsencrypt/live/your-domain.com/privkey.pem" -websocket = true -``` - -### HAProxy (`haproxy.cfg`) +HAProxy acts as a pure TCP proxy — it passes TLS through to the konduit server unchanged. ```haproxy frontend https-ingress @@ -191,10 +33,29 @@ backend konduit-vpn server konduit 127.0.0.1:8443 ``` -`timeout tunnel 0` disables HAProxy's idle-connection timeout for established tunnels. -Without it, connections are killed after 30 seconds of silence. +`timeout tunnel 0` prevents HAProxy from dropping idle connections. -### Client (`client.toml`) +Reload after changes: + +```bash +haproxy -c -f /etc/haproxy/haproxy.cfg # validate first +systemctl reload haproxy +``` + +## Konduit Server (`server.toml`) + +```toml +[server] +listen_addr = "0.0.0.0" +listen_port = 8443 +public_addr = "your-domain.com" +public_port = 443 +tls_cert = "/etc/letsencrypt/live/your-domain.com/fullchain.pem" +tls_key = "/etc/letsencrypt/live/your-domain.com/privkey.pem" +websocket = true +``` + +## Client Configuration (`client.toml`) ```toml [client] @@ -207,17 +68,35 @@ server_public_key = "..." enabled = true ``` -`stealth = true` tells the client to use TLS and WebSocket framing. No other flags needed. +That's it — `stealth = true` activates TLS and WebSocket camouflage automatically. ---- +## Flutter / Mobile App + +Enable **Stealth Mode** in the app settings. The toggle maps to `[stealth] enabled = true` +in the connection config and activates TLS + WebSocket camouflage on all platforms +(Linux, Android, Windows). + +For QR-code-based provisioning, the `t: true` field in the QR payload enables stealth. + +## Verify + +```bash +# TLS cert must be valid — browser must reach your site +curl -s https://your-domain.com/ | head -5 + +# Connect the VPN client +sudo ./konduit --config client.toml + +# Watch HAProxy — you should see the tunnel established +journalctl -u haproxy -f +``` ## Common Mistakes | Mistake | Effect | Fix | |---------|--------|-----| -| `WAIT_END` in inspect rule | 5-second stall on every connect | Use `req.payload(0,1) -m found` | -| Stealth enabled on server (HAProxy-TLS model) | Handshake mismatch after HAProxy strips TLS | Set `[stealth] enabled = false` on server | -| `tls = true` instead of `stealth = true` on client | Connects but drops — no WebSocket framing | Use `[stealth] enabled = true` for TSPU bypass | -| Missing `timeout tunnel 0` in HAProxy | Connection drops after 30 s of silence | Add `timeout tunnel 0` to backend | -| Port 8443 exposed to internet | Bypass HAProxy, no DPI camouflage | Firewall port 8443 to localhost only | +| HAProxy terminating TLS (`bind *:443 ssl crt ...`) | konduit receives plain TCP, WebSocket handshake fails | Use `bind *:443` (no `ssl`) — TCP passthrough | +| Missing `timeout tunnel 0` | Connection drops after 30 s of silence | Add `timeout tunnel 0` to backend | +| Port 8443 exposed to internet | Bypass HAProxy entirely | Firewall port 8443 to localhost only | | Expired/self-signed cert | TLS error on client | Use Let's Encrypt; renew via certbot | +| `stealth = false` on client | Plain TCP connection, DPI detects VPN | Set `[stealth] enabled = true` |