docs(vpn): runbook for the key-issuer, and the gap it leaves open
Records what the gateway routes do, the four things that still have to happen on the servers before the dropdown can light up (UUID pool per inbound, the pool file, X-Forwarded-For on the vhost, deploy-from-repo), and — deliberately at the same prominence — that expiry is bookkeeping rather than enforcement until xray's handler API is wired per box. Also flags the per-client byte cap as an unverified cheaper mitigation, marked as needing a test rather than written up as though it works.
This commit is contained in:
parent
83b65498a2
commit
5a17b0e866
1 changed files with 85 additions and 0 deletions
|
|
@ -68,6 +68,91 @@ Working credentials for the three live exits are in the repo-root
|
|||
`.keys/vpn-servers.json` (gitignored via `.gitignore:70 /.keys/`) for
|
||||
operator testing via the panel's Custom box. They stay there.
|
||||
|
||||
### Built: the gateway side
|
||||
|
||||
Landed in `Argus/src/gateway/public-gateway.mjs` as two routes. Smoke
|
||||
test: `node scratchpad/vpn-session-smoke.mjs` (17 checks, stages the
|
||||
flat VPS layout so it runs the real file).
|
||||
|
||||
**`GET /api/vpn/servers`** — exit list, Reality material only, no UUID.
|
||||
Cached 5 min. This is what the addon's `SERVER_LIST_URL` already points
|
||||
at, so once the pool file exists on the VPS the dropdown lights up with
|
||||
no addon change.
|
||||
|
||||
**`POST /api/vpn/session`** `{ serverId, tier?, address?, ts?, sig? }`
|
||||
→ `{ vless, uuid, serverId, tier, expiresAt, reused }`. Leases one
|
||||
pre-provisioned UUID for `BNS_VPN_TTL_MS` (default 24 h). Re-asking
|
||||
inside the TTL returns the same lease.
|
||||
|
||||
#### What still has to happen on the servers
|
||||
|
||||
1. **Provision a UUID pool per inbound.** Each exit's xray config needs
|
||||
N clients instead of one. On each box, for N=50:
|
||||
```bash
|
||||
for i in $(seq 50); do /usr/local/bin/xray uuid; done
|
||||
```
|
||||
then put them in that inbound's `settings.clients` array (each with
|
||||
`"flow": "xtls-rprx-vision"`) and `systemctl restart xray`.
|
||||
|
||||
2. **Write `/opt/bns-gateway/vpn-pool.json`** on the gateway box. Shape:
|
||||
```json
|
||||
{ "servers": [
|
||||
{ "id": "sm-1", "label": "Silent Mode · 1",
|
||||
"host": "…", "port": 8443, "sni": "www.cloudflare.com",
|
||||
"fp": "chrome", "pbk": "<reality public key>", "sid": "<short id>",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"tiers": ["free","pro","max"],
|
||||
"pool": ["<uuid>", "…"] } ] }
|
||||
```
|
||||
`chmod 600`, owned by the gateway user. Never committed, never
|
||||
uploaded, never served. `tiers` is what gates free/pro/max, so
|
||||
moving an exit between tiers is an edit here, not a code change.
|
||||
|
||||
3. **Add `X-Forwarded-For` to the vhost.** In
|
||||
`VPS/SilentMode/silentmode-st.angie.conf`, the `/api/` location
|
||||
forwards only `Host`. Without the client address the gateway cannot
|
||||
tell callers apart and `/api/vpn/session` answers 503 by design:
|
||||
```
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8700;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr; # <- add
|
||||
}
|
||||
```
|
||||
|
||||
4. **Deploy the gateway from the repo** — per
|
||||
[gateway-deploy-from-repo](../../../../Users/valer/.claude/projects/D--Dev-SilentMode/memory/gateway-deploy-from-repo.md),
|
||||
never edit `/opt/bns-gateway` in place:
|
||||
```bash
|
||||
ssh silentmode 'cp /opt/bns-gateway/public-gateway.mjs /opt/bns-gateway/public-gateway.mjs.bak.$(date +%s)'
|
||||
scp Argus/src/gateway/public-gateway.mjs silentmode:/opt/bns-gateway/
|
||||
ssh silentmode 'systemctl restart bns-gateway'
|
||||
curl https://navigate.st/api/tlds | head # must still say TLD_BEACON
|
||||
curl -s https://navigate.st/api/vpn/servers | head
|
||||
```
|
||||
|
||||
#### The gap this does not close
|
||||
|
||||
`expiresAt` is bookkeeping. xray keeps honouring a leased UUID until the
|
||||
inbound config is rotated, so a TTL returns the slot to the pool but
|
||||
does not disconnect anyone. Two consequences worth holding onto:
|
||||
|
||||
- A user who keeps their UUID after expiry keeps working. Capacity, not
|
||||
access, is what the TTL protects.
|
||||
- Two people can end up on the same UUID over time (issued, expired,
|
||||
re-issued), which weakens attribution.
|
||||
|
||||
Closing it needs xray's handler API reachable per box — enable the `api`
|
||||
inbound in each xray config, run a small localhost agent that does
|
||||
`xray api adduser` / `rmuser`, and have the gateway call those agents
|
||||
over an authenticated channel. At that point `pool` disappears from the
|
||||
pool file and only the Reality material stays.
|
||||
|
||||
A cheaper partial mitigation worth checking first: xray supports a
|
||||
per-client byte `total` with the stats/policy services enabled. If that
|
||||
works on a VLESS+Reality inbound it bounds abuse per UUID without any
|
||||
new service. **Unverified — test before relying on it.**
|
||||
|
||||
### `POST /api/vpn/new-client`
|
||||
|
||||
Request body:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue