docs: add a server quickstart and disclose the UDP relay provisioning

Same audit as konduit-public (c61664c), applied to koridor. The conclusion
is the opposite: there is no server binary and no config format, so nothing
can be documented wrongly. The source backs the README - ffi/src/ssh.rs:389
uses per-flow channel_open_direct_tcpip, with no streamlocal, remote TUN or
forwarded-tcpip anywhere in the crate. No contradictions found; the gap was
thinness.

Added docs/server-quickstart.md. The whole server story was previously one
clause in the README:

- The real requirement (AllowTcpForwarding yes), and that it is already the
  OpenSSH default, so most servers need no configuration at all.
- What you do NOT need: ip_forward, masquerade rules, /dev/net/tun, root.
  Anyone who has set up a VPN server before assumes all four are required.
- Keypair generation, since the user supplies the key; koridor does not
  generate one for the tunnel.
- Host key trust on first connect. The connect is refused until the
  fingerprint is accepted, and a later change is fatal rather than retried.

The finding that justified the work: UDP forwarding provisions itself on the
user's server on first enable - it writes ~/.koridor/udp-relay.sh, appends a
forced-command line to ~/.ssh/authorized_keys marked # koridor-udp-relay, and
requires socat (hardcoded in ffi/src/udp_relay/script.rs:12). So "No special
server-side software required" is true for TCP and false once that toggle is
on. Nothing public said so. It is off by default and idempotent, and
remove_marked_line strips only koridor's own line, leaving every other key
untouched - but a user's authorized_keys and home directory are modified
without them being told. The README now carries that caveat inline.

#k-ops/#6/#212
This commit is contained in:
E. Kaparulin
2026-10-10 22:58:19 +03:00
parent b7e5dad3be
commit 278cbf4fb4
2 changed files with 174 additions and 1 deletions

View File

@@ -24,7 +24,7 @@ Most VPN apps ask you to trust the app vendor's servers. koridor doesn't have an
## Features ## Features
- **Full-device tunnel** — a TUN-based VPN, not just an app-aware SOCKS proxy. Every app on the device is covered, with no per-app setup. - **Full-device tunnel** — a TUN-based VPN, not just an app-aware SOCKS proxy. Every app on the device is covered, with no per-app setup.
- **Bring your own server** — connects to any SSH server with `AllowTcpForwarding yes`. No special server-side software required. - **Bring your own server** — connects to any SSH server with `AllowTcpForwarding yes`. No special server-side software required. (One opt-in exception: UDP port forwarding provisions a relay script and an `authorized_keys` line on your server — see the [Server Quickstart](docs/server-quickstart.md#6-optional-udp-port-forwarding).)
- **Simple onboarding** — set up a connection profile, toggle on, done. - **Simple onboarding** — set up a connection profile, toggle on, done.
- **Fail-closed reconnects** — transient drops are retried with exponential backoff; if the tunnel can't recover, the route is torn down rather than silently leaking traffic in the open. - **Fail-closed reconnects** — transient drops are retried with exponential backoff; if the tunnel can't recover, the route is torn down rather than silently leaking traffic in the open.
- **Tunnel-only DNS** — lookups are resolved through the server's own resolver over the tunnel, never leaked to a third-party DNS provider. - **Tunnel-only DNS** — lookups are resolved through the server's own resolver over the tunnel, never leaked to a third-party DNS provider.
@@ -47,6 +47,11 @@ Releases are published on the [Releases](../../releases) page.
Coming soon. Coming soon.
## Setup
- [Server Quickstart](docs/server-quickstart.md) — what your SSH server needs, the
keypair, host-key trust, and what UDP forwarding provisions on your box
## Architecture ## Architecture
``` ```

168
docs/server-quickstart.md Normal file
View File

@@ -0,0 +1,168 @@
# Running Your Own koridor Server
koridor has no server component. It is a client that speaks standard SSH to a
server you already operate — a VPS, a NAS, a home box, anything running `sshd`.
There is nothing to install on the far end and no koridor-operated backend.
This is the whole server story for normal TCP traffic. One optional feature —
UDP port forwarding — does provision something on your server, and it is
covered separately in step 6 because it changes your `authorized_keys`.
## 1. What you need
- An SSH server you control, reachable from wherever the device will be.
- An SSH keypair. **You supply the key** — koridor does not generate one for the
tunnel, so create it first.
```bash
ssh-keygen -t ed25519 -f ~/.ssh/koridor_key
```
The private key is what you paste into the app. It can have a passphrase; koridor
prompts for it and keeps it in the OS credential store.
## 2. Server requirements (TCP)
`AllowTcpForwarding yes` — and that is the entire requirement.
It is **on by default** in OpenSSH, so most servers need no configuration at
all. Check what yours is actually set to:
```bash
sudo sshd -T | grep -i allowtcpforwarding
```
If it says `no`, enable it in `/etc/ssh/sshd_config`:
```
AllowTcpForwarding yes
```
```bash
sudo sshd -t && sudo systemctl reload sshd
```
`sshd -t` validates the config before the reload — run it, or a typo takes SSH
out remotely.
### What you do *not* need
This trips up people who have set up a VPN server before. None of the following
applies to koridor:
- **No `net.ipv4.ip_forward`.** koridor does not forward packets through the
server; `sshd` opens each outgoing connection itself.
- **No iptables or nftables masquerade rules.** Each TCP flow is an ordinary
SSH `direct-tcpip` channel, so the server makes its own outbound connections
and the kernel never routes between interfaces.
- **No `/dev/net/tun` and no TUN interface on the server.** The TUN device lives
on the client only.
- **No root.** A normal unprivileged SSH user is enough.
- **No konduit-style provisioning, bootstrap, or peer list.** There is no
control API and nothing to reload.
The cost on the server is one SSH session per active TCP flow. A busy browser
tab set will show more `sshd` sessions than you might expect — that is expected,
not a leak.
## 3. Add the profile
In the app, create a connection profile:
| Field | Value |
|---|---|
| Host | Your server's hostname or IP |
| Port | SSH port, `22` unless you changed it |
| Username | The SSH user whose key you installed |
| Private key | Contents of the `.pub`-less private key file (PEM) |
| Passphrase | Only if the key has one |
The host may be a `~/.ssh/config` alias — koridor resolves it and pins the host
key against whatever the alias resolves to, not the alias name.
## 4. Trust the host key
The first connection to a new server does **not** connect. koridor shows you the
host key fingerprint and waits for you to accept it. This is deliberate: the key
is established once, on your say-so, and never silently assumed.
Compare the fingerprint against the one SSH already knows:
```bash
ssh-keygen -lf /etc/ssh/ssh_ed25519_key.pub
```
If the server is remote, compare against the fingerprint `ssh` shows you on
first connect from a machine you trust.
Once pinned, koridor checks the key on every connection. If it later changes,
koridor stops and tells you — it does not retry or fall back, because a changed
host key means you are no longer talking to the same machine.
## 5. Connect
Toggle the profile on. Traffic routes through the tunnel, and DNS lookups go
through the server's own resolver over the tunnel rather than to a third-party
resolver.
If the tunnel cannot recover after a drop, koridor tears the route down instead
of leaving traffic to leak in the open. That is the intended behaviour, not a
bug.
Transient failures retry with exponential backoff up to roughly two minutes.
Failures that retrying cannot fix — a private key that will not decode, a wrong
passphrase, a rejected public key — fail immediately instead of sitting out that
schedule.
## 6. Optional: UDP port forwarding
**Off by default.** SSH has no UDP channel type, so koridor relays specific
allow-listed UDP ports through a second, restricted SSH connection.
Enabling it **provisions itself on your server**, automatically, the first time
you turn it on:
- Installs a managed script at `~/.koridor/udp-relay.sh` for the SSH user.
- Adds one line to that user's `~/.ssh/authorized_keys`, marked
`# koridor-udp-relay`, restricted with a forced command so the key can only
run that relay.
- Requires **`socat`** on the server.
```bash
sudo apt install socat # or: dnf install socat
```
Both changes are idempotent and reversible — clearing the allow-list removes the
`authorized_keys` line, leaving every other key in the file untouched. Every
other line in `authorized_keys` is preserved across re-syncs.
Ports are gated by destination port only, not destination IP: any host on an
allow-listed port can be relayed to. Datagrams are piped through `socat` rather
than framed, so two datagrams written back to back are not guaranteed to arrive
as two separate reads — fine for WireGuard and similar retrying protocols, not
fine as a general UDP transport.
The main use case is running another UDP-based tool, most often a second VPN
like WireGuard, through koridor.
## 7. Optional: TCP local forwards
For per-app routing without the TUN, a profile can carry classic SSH local
forward rules in `LOCAL_PORT:IP:PORT` form. These are ordinary `-L` forwards and
need nothing extra on the server.
## Troubleshooting
| Symptom | Cause |
|---|---|
| Refuses to connect, no fingerprint shown | A profile with no pinned host key. Accept the fingerprint on first connect. |
| "host key changed" error | The server's key really changed — a rebuild, or you are not talking to the same host. Investigate before accepting the new one; koridor will not retry. |
| Connects, but no traffic | Check `AllowTcpForwarding` with `sshd -T`. |
| Works with `ssh`, fails in the app | Usually the key. koridor wants the private key contents, not a path, and the key must be PEM. |
| Sits retrying for two minutes then fails | A fatal error being treated as transient: bad key, wrong passphrase, or rejected public key. These normally fail at once — if yours does not, the error text is the useful part. |
| Second VPN (WireGuard) comes up but passes no traffic | WireGuard is UDP and UDP forwarding is off. See step 6. |
| UDP forwarding fails at setup | `socat` missing on the server, or the SSH user cannot write to its own `~/.ssh/`. |
Logs follow the usual pattern: connection failures retry on a backoff schedule,
and anything that retrying cannot fix is reported immediately rather than
silently repeated.