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.
- Your service authenticates the operator and authorizes access to the selected controller and DevKit. Allocate a session and separate client/controller grants.
- Publish
devices.ssh.openthrough the controller's authenticated control channel using the MQTT command envelope. - 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.
- 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>, wheresecretis 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:simaoruser.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.
| Direction | Message | Meaning |
|---|---|---|
| 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.