Skip to main content

Browser terminal access

Kerrigan provides devices.ssh.open commands for cloud services that integrate remote terminal access into their own console or client. The controller opens an outbound WSS connection only for the requested session, then opens SSH and a PTY to the selected DevKit. No permanent WebSocket, inbound SSH forwarding, or cloud WireGuard tunnel is required.

The integration supplies operator authentication, session authorization, and a WebSocket relay implementing the protocol below. Kerrigan owns the local device reservation, SSH credentials, and PTY. The feature does not require a particular cloud provider, relay hosting model, or frontend library.

Integration flow​

Open the sequence diagram in a full page.

The pairing response establishes which relay endpoints Kerrigan trusts; it does not open a persistent terminal connection. Each authorized session gets separate client and controller grants, a new controller WSS connection, and its own SSH PTY. The cloud API and relay are logical roles and can share the same backend.

  1. Your service authenticates the operator and authorizes access to the selected controller and DevKit. Allocate a session and separate client/controller grants.
  2. Publish devices.ssh.open through the controller's authenticated control channel using the MQTT command envelope.
  3. Connect the operator's terminal client to your relay. Kerrigan connects outbound with its controller grant; the relay pairs only the two authorized session peers.
  4. Forward terminal frames and acknowledgements between the peers. On revocation, expiry, or either peer disconnecting, close the paired connections.

Your session-management API and client authentication scheme are integration choices. The currently shipped command transport uses an AWS IoT adapter and its existing topic names, documented in the MQTT API; this does not make the terminal operation or WSS relay specific to that provider. Supporting another command transport requires an adapter, not a change to the terminal protocol.

Paired-cloud defaults​

Terminal access is enabled automatically when the authenticated cloud pairing response advertises terminal endpoints. The cloud returns this profile field:

{
"terminal": {
"websocket_urls": ["wss://terminal.example.com/sessions"]
}
}

Kerrigan persists this metadata in its paired profile and reads it for each open request. Only those exact WSS URLs are accepted. A command cannot select a relay outside the paired profile. Missing pairing credentials or terminal endpoints prevent opening a session. The feature is cloud-agnostic; each integration provisions its own endpoints through its pairing response.

The default permitted SSH logins are sima and user. The selected account must exist on the DevKit and accept the configured KERRIGAN_FIRMWARE_SSH_PASSWORD. This version uses that same configured password for either account; permitting a login does not create the account or configure its password. NeatOps currently selects sima. SSH inherits the account's existing privileges and sudo permissions.

terminal-access.json, allowed_logins, and known_hosts_file are deprecated and no longer read. Existing files do not enable, disable, or restrict access. SSH host keys are accepted without enrollment, pinning, or persistence, including changed keys. This deliberately removes SSH server identity verification; SSH login authentication and cloud authorization remain in place. Targets still come only from Kerrigan's local inventory, not arbitrary caller-supplied addresses.

Existing paired appliances​

Profiles created before terminal metadata was introduced must be refreshed by pairing again with the updated cloud service, or have the same terminal field provisioned into their existing profile through trusted appliance administration. An application upgrade alone cannot discover a relay that the original pairing response never supplied. Do not copy certificates or private keys from another appliance. Provisioning terminal metadata preserves the appliance's identity and does not require the legacy terminal files. No restart is needed for metadata changes once this connector version is installed.

Session command​

devices.ssh.open uses the normal control envelope. See the MQTT request and result examples. Its arguments are:

  • target_ids: exactly one enrolled DevKit ID.
  • session_id: 32 hexadecimal characters.
  • websocket_url: exact match against the paired profile endpoints.
  • controller_ticket: one-use controller grant in the format <session_id>.controller.<secret>, where secret is 43 base64url characters (A–Z, a–z, 0–9, _, -). Generate it from 32 cryptographically random bytes encoded without padding. Kerrigan keeps it only in memory.
  • login: sima or user.
  • protocol_version: 1.
  • grant_deadline: UNIX seconds, at most 60 seconds after cloud creation.
  • deadline: UNIX seconds, at most 30 minutes after cloud creation.
  • idle_seconds: at most 600 seconds.

The dispatcher redacts the ticket before persisting command history. MQTT command IDs are deduplicated; a duplicate must never create another PTY. Restarted sessions are not resumed with a persisted token. Errors returned to MQTT contain no URLs, credentials, tickets, or terminal output.

The device must be online and idle and have no active execution lease. Kerrigan temporarily quarantines/drains it, sets an execution lease marker, and restores it when the session ends. Startup restores stale terminal-specific drain markers because SSH connections do not survive a controller process restart. Explicit management operations such as reboot/firmware updates are separate controls; operators should not initiate them against a device in a terminal session.

Relay protocol v1​

Kerrigan connects to the allowlisted URL with a ticket query parameter containing controller_ticket. The configured URL must use wss:// and must not contain credentials, a query string, or a fragment. The relay must validate the ticket, its role and session, and its grant deadline, then consume it once. The client grant must not allow impersonating the controller. Do not log grant-bearing URLs; configure proxy/access logging accordingly. Browser clients also require an explicit origin policy at the relay.

Messages are JSON text frames. The relay must validate direction and frame shape, forward peer traffic without changing sequence numbers, and prevent traffic from crossing session boundaries. A generic WebSocket echo server is insufficient.

DirectionMessageMeaning
Kerrigan → relay{"action":"hello"}Announces the connected controller.
Kerrigan → relay{"action":"ping"}Application heartbeat, every 30 seconds.
Relay → Kerrigan{"action":"attached"}, {"action":"waiting"}, or {"action":"pong"}Connection/heartbeat notifications; ignored by the controller pump.
Kerrigan → client{"action":"ready","seq":1}SSH PTY is ready; acknowledge before further output.
Kerrigan → client{"action":"output","seq":2,"data":"..."}Base64-encoded terminal bytes.
Client → Kerrigan{"action":"input","seq":1,"data":"..."}Base64-encoded input bytes.
Client → Kerrigan{"action":"resize","seq":2,"cols":120,"rows":40}PTY dimensions; each integer must be between 1 and 500.
Either peer → other peer{"action":"ack","seq":1}Acknowledges a frame in the opposite direction.
Kerrigan → client{"action":"exit","seq":3,"code":0}Shell exit status, clamped to 0–255.
Kerrigan → client{"action":"error","seq":1,"code":"login_failed"}SSH setup failure; the connection then closes.

Sequence numbers in the examples are illustrative. Client input and resize share one counter starting at 1. Controller ready, output, and exit share an independent counter starting at 1. Acknowledge only after accepting the frame; duplicates must be acknowledged without applying their contents twice. Setup errors may use host_key_rejected, login_failed, or ssh_unavailable and are best-effort notifications before disconnect. MQTT command events also report operation completion or failure.

The controller retries an unacknowledged output frame at one-second intervals, up to ten sends before terminating. Clients should similarly retry input with the same sequence number. The relay must handle startup ordering so ready can reach a client that connects after Kerrigan. It should not acknowledge data on behalf of an absent peer. Kerrigan accepts WebSocket messages up to 24 KiB.

The relay enforces its own session expiry, authorization, and revocation policy. Revoking a grant alone does not terminate an established session: close its WSS connections. Kerrigan independently enforces the supplied hard and idle deadlines and permits at most two active terminal sessions per controller.

Transport and termination​

The SSH PTY uses xterm-256color. Terminal bytes are base64-encoded in JSON with 12 KiB maximum raw data per frame. Browser input and controller output use separate sequence counters and end-to-end acknowledgements. Each direction permits one unacknowledged frame; duplicate input is acknowledged without re-execution, and sequence gaps terminate the connection. Resize updates the SSH PTY dimensions.

The controller stops reading SSH output while waiting for an output acknowledgement, bounding memory use. It sends heartbeats every 30 seconds. Heartbeats/output do not extend the idle timeout; only new browser input/resize does.

Closing the browser, revoking the cloud session, losing WSS, reaching the hard deadline, or idling out closes the SSH channel and releases the device reservation. The integrating service can issue devices.ssh.close with the exact session ID to cancel an active terminal through MQTT, or close its paired WSS connections. The close command waits for local cleanup and is idempotent for inactive IDs. It remains available after terminal access is unpaired. Revoke cloud grants too: a close received before the open is registered does not prevent that later open. Each session has its own controller connection and SSH PTY, so sessions for different DevKits on the same Kerrigan terminate independently. There is no reconnect/resume in v1. Background processes intentionally detached from the shell may survive: this is remote admin access, not a process sandbox. Terminal transcripts are not recorded by this version.

Integration verification​

Validate a deployment with an enrolled test DevKit:

  • Open a session through your operator UI and verify the SSH identity and target.
  • Exercise terminal input/output, PTY resize, and Ctrl+C.
  • Confirm duplicate input is not executed twice and sequence gaps close the session.
  • Reject expired/reused grants, wrong-role grants, unauthorized operators, and disallowed browser origins at the relay.
  • Revoke an active session, disconnect each peer, and exercise both timeouts; confirm the SSH channel closes and the device reservation is released.
  • Confirm tickets, SSH credentials, and terminal contents are absent from logs.

Deploy a Kerrigan image containing the connector and persist its paired cloud profile across container replacement. Deployment-specific endpoints, operator policies, rollout records, and test reports belong in the integrating service's documentation.