chore: bump version to 0.1.0-beta.7
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||

|
```
|
||||||
|
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` |
|
||||||
|
|||||||
Reference in New Issue
Block a user