@@ -0,0 +1,61 @@
|
||||
# gssh integration
|
||||
|
||||
How `gssh-mcp` will reuse the existing gssh server. Decided in Phase-0 planning; implementation in Phase 3.
|
||||
|
||||
## Why reuse
|
||||
|
||||
The gssh server at `gssh.alexandru.macocian.me` already:
|
||||
- Authenticates inbound requests via Authentik JWTs (`HttpContext.User`).
|
||||
- Resolves the per-user host allow-list (`SessionController.GetHosts` → `SshConnectorService.Hosts`).
|
||||
- Establishes SSH sessions internally using the host's CA-signed credentials (the operator never sees an SSH key).
|
||||
- Streams stdin/stdout as a binary WebSocket and accepts text control messages for terminal resize (`SessionSocketRoute`).
|
||||
|
||||
That's the entire job of "let an authorized human run a command on a Charlie host". Building a parallel SSH broker inside sherlock would duplicate the CA integration, the host-allow-list logic, and the audit trail — and put another piece of cert-handling code in our blast radius. We do not do that.
|
||||
|
||||
## What `gssh-mcp` actually is
|
||||
|
||||
A thin Go HTTP+WebSocket client to the existing gssh server, wrapped in a stdio MCP. It:
|
||||
|
||||
1. Reads `GSSH_TOKEN` from env at startup (an Authentik JWT minted by the broker, `aud=gssh`).
|
||||
2. Calls `POST /api/v1/session/initialize` with `Authorization: Bearer <jwt>` to materialise the user's session on the gssh server.
|
||||
3. Caches the list of permitted hosts from `GET /api/v1/session/hosts`.
|
||||
4. Exposes MCP tools:
|
||||
- `ssh.list_hosts()` — returns the cached host list.
|
||||
- `ssh.run(host, command, timeout?)` — opens a WebSocket to the session route for `host`, writes `command + "; echo __SHERLOCK_DONE__$?\n"`, reads until it sees the sentinel, returns `{stdout, exit_code}`.
|
||||
- `ssh.put_file(host, path, content)` (later) — same shell channel, base64-decode + `tee` on the far side, sentinel as above.
|
||||
5. On 401, asks the broker for a refreshed token via the UDS RPC and retries once.
|
||||
|
||||
No SSH key handling. No cert minting. No `~/Dev/gssh` shell-out.
|
||||
|
||||
## Endpoints sherlock relies on (gssh contract)
|
||||
|
||||
| Method | Path | Used for |
|
||||
|---|---|---|
|
||||
| `POST` | `/api/v1/session/initialize` | Materialise the user's session before opening the WebSocket. |
|
||||
| `GET` | `/api/v1/session/hosts` | Per-user host allow-list. |
|
||||
| `GET` | `/api/v1/users/me` | Sanity check / debug. |
|
||||
| `WS` | `/<session-socket-route>?hostName=<host>` (exact path defined in gssh's routing) | Bidirectional binary stream = stdin/stdout of the SSH session. Text frames are JSON control messages (e.g. `ResizeMessage`). |
|
||||
|
||||
If a gssh release ever changes one of these, `gssh-mcp` is the only thing in sherlock that needs to follow.
|
||||
|
||||
## Token shape
|
||||
|
||||
- Issuer: `https://id.alexandru.macocian.me/application/o/gssh/`
|
||||
- Audience: `gssh.alexandru.macocian.me`
|
||||
- Service kind in the registry: `oidc-federated`
|
||||
- `exchange.mode = "passthrough"` is safe because gssh already accepts the operator's Authentik ID token (same audience model gssh's web UI uses today). Switch to `rfc8693` only if we ever introduce per-tool scope splitting (e.g. read-only vs write).
|
||||
|
||||
## Completion sentinel — open detail for Phase 3
|
||||
|
||||
The gssh WebSocket is interactive (PTY-style). `gssh-mcp` needs a deterministic way to know a command has finished. Two options:
|
||||
|
||||
1. **Client-side wrap:** `gssh-mcp` sends `printf '%s\n' "<cmd>; echo __SHERLOCK_DONE__$?" | <session-shell>` and parses for the marker.
|
||||
2. **Server-side one-shot mode:** add a small endpoint / route flag on the gssh server that runs a single command and closes the socket with the exit code in the close frame. Cleaner, but it's a gssh change.
|
||||
|
||||
Phase 0 decision: try (1) first because it requires no gssh change; revisit (2) if we hit edge cases (TTY echo, prompt clutter, multi-line stdout truncation).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- `gssh-mcp` does NOT speak gssh's WebSocket protocol "directly" by hand-rolling SSH packet framing. It just speaks the documented `/api/v1/session/*` HTTP + WS surface.
|
||||
- `gssh-mcp` does NOT shell out to `/mnt/seagate/Dev/gssh` (which is the C# server source, not a client binary).
|
||||
- `gssh-mcp` does NOT enforce per-host policy locally. The gssh server already does that against JWT claims; duplicating it client-side is a footgun.
|
||||
Reference in New Issue
Block a user