chore: bump version to 0.1.0-beta.7

This commit is contained in:
E. Kaparulin
2026-06-26 23:16:30 +03:00
parent 87bb99c10b
commit fb7ad7ec40

View File

@@ -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 Stealth mode makes VPN traffic look like normal HTTPS to deep packet inspection systems.
real TLS with a valid certificate. Deep packet inspection sees a standard TLS 1.3 The client opens a real TLS 1.3 connection, performs a WebSocket upgrade, and then carries
connection to a legitimate domain — not a VPN. the konduit protocol inside WebSocket binary frames — identical in appearance to any
browser-based web app.
![Stealth mode architecture](diagrams/stealth-architecture.svg) ```
Client ──TLS 1.3──▶ HAProxy :443 (TCP passthrough) ──▶ Konduit :8443
## How It Works HTTP/1.1 GET /ws → 101 Switching Protocols
WebSocket binary frames (konduit protocol inside)
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.
## Server Requirements ## Server Requirements
- A domain with a valid TLS certificate (Let's Encrypt / certbot) - A domain with a valid TLS certificate (Let's Encrypt / certbot)
- HAProxy 2.4+ - HAProxy 2.4+
- Konduit server on a non-public port (e.g. `8443`) - 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 ## HAProxy Configuration
Combine the certificate and private key into a single PEM file for HAProxy: HAProxy acts as a pure TCP proxy — it passes TLS through to the konduit server unchanged.
```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 ```haproxy
frontend https-ingress frontend https-ingress
@@ -191,10 +33,29 @@ backend konduit-vpn
server konduit 127.0.0.1:8443 server konduit 127.0.0.1:8443
``` ```
`timeout tunnel 0` disables HAProxy's idle-connection timeout for established tunnels. `timeout tunnel 0` prevents HAProxy from dropping idle connections.
Without it, connections are killed after 30 seconds of silence.
### 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 ```toml
[client] [client]
@@ -207,17 +68,35 @@ server_public_key = "..."
enabled = true 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 ## Common Mistakes
| Mistake | Effect | Fix | | Mistake | Effect | Fix |
|---------|--------|-----| |---------|--------|-----|
| `WAIT_END` in inspect rule | 5-second stall on every connect | Use `req.payload(0,1) -m found` | | HAProxy terminating TLS (`bind *:443 ssl crt ...`) | konduit receives plain TCP, WebSocket handshake fails | Use `bind *:443` (no `ssl`) — TCP passthrough |
| Stealth enabled on server (HAProxy-TLS model) | Handshake mismatch after HAProxy strips TLS | Set `[stealth] enabled = false` on server | | Missing `timeout tunnel 0` | Connection drops after 30 s of silence | Add `timeout tunnel 0` to backend |
| `tls = true` instead of `stealth = true` on client | Connects but drops — no WebSocket framing | Use `[stealth] enabled = true` for TSPU bypass | | Port 8443 exposed to internet | Bypass HAProxy entirely | Firewall port 8443 to localhost only |
| 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 |
| Expired/self-signed cert | TLS error on client | Use Let's Encrypt; renew via certbot | | 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` |