docs: document the agent-relayed console proxy

Every console session now rides the agent's outbound gRPC connection
instead of a direct guacd-to-target dial, so it works for servers
behind NAT and now requires a live agent (409 agent_offline
otherwise). Documents PROXY_ADVERTISE_HOST / PROXY_LISTEN_HOST and
corrects reachability claims across the docsite and CLAUDE.md.
This commit is contained in:
2026-07-31 09:10:05 +01:00
parent 119d8694d1
commit 1e1546cb60
5 changed files with 42 additions and 13 deletions
@@ -17,6 +17,8 @@ it is absent.
| `KEY_ENCRYPTION_KEY` | yes in practice | | 64 hex characters (32 bytes) for AES-256-GCM. Required for private keys, vault secrets, OIDC client secrets and console credentials |
| `GITEA_HOST` | yes | `gitea.example.com` | Used to build the install scripts and agent download URLs. The default is a placeholder that will not resolve |
| `GUACD_ADDR` | no | `guacd:4822` | The [browser console](../vantage/browser-console.md) daemon |
| `PROXY_ADVERTISE_HOST` | no | `server` | The hostname **guacd** uses to reach the control plane's console relay. Wrong here and every console session fails at connect with guacd unable to resolve the relay |
| `PROXY_LISTEN_HOST` | no | `0.0.0.0` | Interface the ephemeral relay listeners bind. Narrow it only if guacd shares a known interface |
| `APP_ROOT_LABEL` | no | `vantage` | The app root label for the host and session organisation guard |
| `VANTAGE_WORKFLOW_LOG_DIR` | no | | Where workflow run logs are written |
+12 -5
View File
@@ -24,7 +24,8 @@ flowchart LR
W --> S["server :8080"]
A["Agent on a managed server"] -->|"gRPC/TLS :9090, outbound"| S
S --> G["guacd :4822"]
G -->|"SSH / RDP / VNC"| T["Target machine"]
G -->|"relayed over the :9090 stream"| A
A -->|"SSH / RDP / VNC, loopback"| T["Target machine (same host as agent)"]
```
Two things are worth reading off that diagram.
@@ -33,9 +34,13 @@ Two things are worth reading off that diagram.
NAT is not an obstacle. The only requirement is that the machine can reach
`GRPC_HOST`.
**The console does not use the agent.** guacd connects directly to the target on
the protocol port. A machine reachable only by its agent behind NAT, on a
private subnet cannot be consoled, even though every other feature works.
**The console rides the agent's connection too.** guacd never dials the target
directly; the server pushes a command down the agent's existing outbound gRPC
stream on `9090`, and the agent relays the protocol traffic from its own
loopback. No route from the control plane to the target's address is needed,
and no new inbound port opens on the target — the same connection that carries
key sync carries console traffic. This is what makes the console work for a
machine behind NAT on a private subnet, as long as its agent is online.
## What to open
@@ -49,7 +54,9 @@ private subnet cannot be consoled, even though every other feature works.
- `gitea.hostxtra.co.uk`, for agent releases and version checks.
- Anything a server-run [monitor](../vantage/monitors.md) checks.
- SMTP, if you use an SMTP notification channel.
- Protocol ports on machines you intend to console.
No route to the machines you intend to console is needed — that traffic rides
the agent's existing outbound `9090` connection instead.
### Outbound from a managed machine
+1 -1
View File
@@ -86,7 +86,7 @@ instantaneous.
| Connects, then closes at once | guacd unreachable. Check `GUACD_ADDR` and that the container is running |
| SSH rejects the key | The stored key has no private half, or is not on the target |
| RDP fails on retry | Credentials are single-use and consumed at tunnel open enter them again |
| Hangs at "connecting" | The **control plane** cannot reach the target on the protocol port. The agent's reachability is irrelevant here |
| Hangs at connecting | The agent is offline, or nothing is listening on the protocol port on the target's own loopback address. Check the audit log for `console.proxy_failed` its reason names which |
| Fails only in production | The reverse proxy is not forwarding WebSocket upgrade headers |
## Monitors report down when the service is up
+9 -6
View File
@@ -15,17 +15,20 @@ to a **guacd** daemon and manages credentials around it.
- `guacd` running and reachable from the server. The bundled Compose stack
includes it; `GUACD_ADDR` defaults to `guacd:4822`.
- `KEY_ENCRYPTION_KEY` set, since every credential involved is stored encrypted.
- Network reachability **from the control plane to the target** on the protocol
port. This is the one part of Vantage that is not agent-mediated: guacd
connects directly, so a machine reachable only by its agent cannot be
consoled.
- The target's **agent must be online**. Console traffic is relayed over the
agent's existing outbound connection, so the control plane never needs a route
to the server's address — but it does need the agent.
- No inbound port on the target, beyond what the protocol already listens on
locally. A service bound only to `127.0.0.1` works, because the agent dials
loopback on the target itself.
## Opening a session
From a server's page, choose **Console**. Then:
1. The UI calls `POST /api/console/connect`, which mints a **one-time** session
token.
token. If the target's agent is not connected, this fails immediately with
`409 agent_offline` rather than hanging.
2. The browser opens a WebSocket to `GET /api/console/tunnel` with that token.
3. The server marks the token consumed atomically, so a second use cannot
race and proxies the connection to guacd.
@@ -67,4 +70,4 @@ keystroke log. If you need that, it has to come from the target machine.
| Connects then closes immediately | guacd unreachable check `GUACD_ADDR` and that the container is up |
| SSH refuses the key | The stored key has no private half, or is not in the target's `authorized_keys` |
| RDP fails on a fresh credential | Credentials are consumed on open; a retry needs them entered again |
| Hangs at connecting | The control plane cannot reach the target on the protocol port |
| Hangs at connecting | The agent is offline, or nothing is listening on the protocol port on the target's own loopback address. Check the audit log for `console.proxy_failed` — its reason names which |