Skip to content

SSH gateway for docker-sbx hosts #14

Description

@czpython

Problem

drukbox's contract assumes each host runs an SSH server that callers dial directly. Docker Sandboxes (sbx) does not work that way: its data plane is a session API, not an open port.

Findings from the docker-sbx deployment tests (#13):

  • The daemon stops a sandbox approximately 30 seconds after its last sbx session ends.
  • Connections to published ports do not count as sessions, and a caller cannot wake a stopped sandbox.
  • Published port bindings change across a stop/wake cycle, thus stored port coordinates go stale.

Decision: the drukbox-gateway process

A new process in this repo, run the same way as the janitor and the pool: same codebase, same settings, direct database access, own systemd unit.

  • Callers connect with normal SSH and the per-host key: ssh <host-name>@<gateway>.
  • The gateway authenticates the presented key against the stored per-host public key, resolves the host, and asks the host's provider to dial a session.
  • For docker-sbx, the dial is an sbx exec session. The connection is then a real daemon session: a stopped sandbox wakes on connect (~6 s, measured), stays awake while connected, and sleeps after disconnect. The auto-stop becomes scale-to-zero.
  • Classic VM providers (exe, aws, hetzner, exoscale, docker) keep the direct path. No gateway.

Prior art that shaped the decision: ContainerSSH (terminate SSH once, authenticate externally, dispatch to a per-connection backend), and the vendor gateways that clients such as Crabbox consume (Tenki ssh-proxy, Morph's shared gateway). Session-API products (e2b, Modal) expose no SSH at all — the service in front of them must provide the SSH face.

Design

  • Auth: a new hosts.public_key column stores the public half of the per-host keypair (the private half stays return-once, never persisted). The key is the identity; the username must match the host name as a second check.
  • Provider seam: a SessionCapability ABC in providers/capabilities.py, the same pattern as HttpProxyCapability. A provider that implements it can be dialed by the gateway. docker-sbx is the first implementation.
  • Contract: for hosts of a session provider, the API response carries the gateway address and port in the existing fields (external_ssh_host, external_ssh_port, ssh_username = host name). scan_known_hosts works unchanged and pins the gateway's host key.
  • Server: asyncssh, python -m gateway.server. Interactive shell and command execution only. SFTP and port forwarding are refused.
  • Placement: for docker-sbx the gateway runs on the sandboxd machine.

Scope

  • M1: migration, SessionCapability, the gateway process, the docker-sbx dialer, tests.
  • M2: deploy documentation, systemd unit, end-to-end verification on a real host.
  • Not in v1: SFTP, scp, port forwarding, multiple gateways.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions