# Stealth Mode: Real TLS Bypass via HAProxy 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 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. The `stealth = true` and `tls = true` config flags are equivalent. Both activate TLS wrapping on the client. Existing configs with `stealth = true` continue to work without modification. ## 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`) ```toml [client] server_endpoint = "your-domain.com:443" tls = true peer_id = "..." identity_key = "..." server_public_key = "..." ``` The legacy flag is identical: ```toml [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 wrapping 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 ``` ## 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 | Handshake mismatch after HAProxy strips TLS | Set `[stealth] enabled = false` | | 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 |