diff --git a/README.md b/README.md index e4fd12b..af93540 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ volumes: portwing-stacks: ``` -By design, the `sockguard.yaml` preset above (a copy of sockguard's `portwing.yaml`) denies all exec — `POST /containers/*/exec` returns a 403 with an `exec denied: no commands are allowlisted` reason. If Drydock's edge exec feature is in play, use [`examples/docker-compose.edge-with-exec.yml`](examples/docker-compose.edge-with-exec.yml) instead, which pairs edge mode with sockguard's `portwing-with-exec.yaml` preset (`examples/sockguard-with-exec.yaml`) so exec is actually allowed through the proxy. Either way, denial reasons like `exec denied: no commands are allowlisted` now surface in the controller's exec error instead of a bare status code. +By design, the `sockguard.yaml` preset above (a copy of sockguard's `portwing.yaml`) denies all exec — `POST /containers/*/exec` matches no allow rule and hits the catch-all deny, returning a 403 with a `not allowed by portwing preset` reason. If Drydock's edge exec feature is in play, use [`examples/docker-compose.edge-with-exec.yml`](examples/docker-compose.edge-with-exec.yml) instead, which pairs edge mode with sockguard's `portwing-with-exec.yaml` preset (`examples/sockguard-with-exec.yaml`) so exec is actually allowed through the proxy; under that preset, policy denials carry specific reasons instead, like `exec denied: privileged exec is not allowed`. Either way, the denial reason now reaches Portwing's exec error and is forwarded in the `exec_end` frame's reason to the controller (drydock-side display of it is still pending). **Upgrade to Ed25519 key auth (zero shared secrets):** generate a keypair with `portwing keygen`, mount the `authorized_keys` file, and set `AUTHORIZED_KEYS=/etc/portwing/authorized_keys` — see [Authentication](#authentication). Use `PRIVATE_KEY_FILE` for signed edge-mode hellos. diff --git a/docs/content/docs/drydock-integration.mdx b/docs/content/docs/drydock-integration.mdx index 36c266f..a8faddb 100644 --- a/docs/content/docs/drydock-integration.mdx +++ b/docs/content/docs/drydock-integration.mdx @@ -269,6 +269,26 @@ When `PRIVATE_KEY_FILE` is set, the hello carries the `pubKeyId`, `timestamp`, ` | `ping` / `pong` | Either | Keepalive (30s default) | | `error` | Either | Error with optional `code` | +**Exec (interactive `docker exec` sessions):** + +| Type | Direction | Purpose | +|---|---|---| +| `exec_start` | Controller → Agent | Start an exec session (`execId`, `containerId`, `cmd`, `user`, `cols`, `rows`, `tty`) | +| `exec_ready` | Agent → Controller | Exec session started and attached; safe to send input | +| `exec_input` | Controller → Agent | Base64 stdin chunk for a live session | +| `exec_output` | Agent → Controller | Base64 stdout/stderr chunk from a live session | +| `exec_resize` | Controller → Agent | TTY resize (`cols`, `rows`) for a live session | +| `exec_end` | Either | Session ended, with an optional `reason` | + +If a session fails to start (for example, a sockguard exec-policy denial), +the agent's failStart path sends `exec_end` immediately instead of +`exec_ready`. Its `reason` field now carries the underlying Docker/sockguard +error body — e.g. `not allowed by portwing preset` or `exec denied: +privileged exec is not allowed` — rather than a bare status code. Drydock's +edge agent adapter does not yet surface that reason to the user; today it +receives the frame but discards `reason` before it reaches Drydock's own +exec error display. + **Drydock-specific (`dd:` namespace):** | Type | Direction | Purpose | diff --git a/examples/README.md b/examples/README.md index 12e8305..ab9e35d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,6 +7,8 @@ Ready-to-run Docker Compose and Kubernetes examples, hardened by default (`read_ | [`docker-compose.standard.yml`](docker-compose.standard.yml) | Standard (inbound HTTP on :3000) | Drydock or any client can reach the host directly | | [`docker-compose.edge.yml`](docker-compose.edge.yml) | Edge (outbound WebSocket, no inbound ports) | Agent is behind NAT/firewall; it dials out to your Drydock instance | | [`docker-compose.with-sockguard.yml`](docker-compose.with-sockguard.yml) | Standard + [sockguard](https://github.com/CodesWhat/sockguard) socket filter | Two-layer defense: even a compromised agent is constrained to an explicit Docker API allowlist | +| [`docker-compose.edge-with-exec.yml`](docker-compose.edge-with-exec.yml) | Edge + sockguard, exec enabled | Same two-layer defense as above, but pairs with [`sockguard-with-exec.yaml`](sockguard-with-exec.yaml) so Drydock's edge exec feature is actually allowed through the proxy | +| [`sockguard-with-exec.yaml`](sockguard-with-exec.yaml) | Sockguard preset (with-exec) | The policy file `docker-compose.edge-with-exec.yml` mounts; pairs with it, not used standalone | | [`observability/docker-compose.yml`](observability/docker-compose.yml) | Standard or edge + Prometheus + Fluent Bit | Complete TLS/auth, metrics, readiness, and audit-shipping topology selected with a Compose profile | Before starting any of them, generate a token and export the Docker socket's group ID (the images run as the non-root `portwing` user, UID 65532, and need `group_add` to reach the socket): diff --git a/examples/docker-compose.edge-with-exec.yml b/examples/docker-compose.edge-with-exec.yml index be1a952..d93f3c3 100644 --- a/examples/docker-compose.edge-with-exec.yml +++ b/examples/docker-compose.edge-with-exec.yml @@ -8,12 +8,16 @@ # stack uses sockguard-with-exec.yaml, not sockguard.yaml. # # sockguard.yaml (the plain preset used by docker-compose.with-sockguard.yml) -# denies all exec paths by design — POST /containers/*/exec comes back 403 -# "exec denied: no commands are allowlisted". If drydock is going to drive -# interactive exec through this agent's edge tunnel, sockguard has to be -# running the portwing-with-exec.yaml preset (sockguard-with-exec.yaml here) -# instead, or every exec attempt is denied at the proxy before it ever -# reaches Portwing. +# denies all exec paths by design — POST /containers/*/exec matches no allow +# rule and hits the catch-all deny, coming back 403 "not allowed by portwing +# preset". If drydock is going to drive interactive exec through this +# agent's edge tunnel, sockguard has to be running the portwing-with-exec.yaml +# preset (sockguard-with-exec.yaml here) instead, or every exec attempt is +# denied at the proxy before it ever reaches Portwing. +# +# For an audited, version-pinned variant of this same stack, see sockguard's +# examples/compose/tri-tool/ bundle (pins SOCKGUARD_VERSION/PORTWING_VERSION/ +# DRYDOCK_VERSION with override env vars). # # Edge mode against Drydock 1.6 is Ed25519-only: the controller's # /api/portwing/ws endpoint rejects token-based hellos. Generate a keypair