Repository navigation
SSH Gateway
TL;DR
| Field | Value |
|---|---|
| What | Native ssh access to fleet servers, terminated at the Hub instead of connecting straight to each node |
| Who | Operators who already have vey (see CLI) and want a real terminal (full-screen editors, htop, window resize) instead of the browser terminal |
| Why | The Hub brokers SSH access and checks live authorization; enrolled agents provide the execution stream, without requiring per-node SSH keys or sshd reachability |
| Where | A dedicated TCP port on the Hub (default :2222), separate from the HTTP/gRPC ports |
| How |
vey ssh-cert to obtain a short-lived certificate, then vey ssh <server> (or a manually-constructed ssh command) to connect |
- Overview
- Prerequisites
- Enrollment:
vey ssh-cert -
Connecting:
vey ssh <server> - No-PTY command mode
- The manual
sshform and theuser+serverlogin name - Host-key pinning
- Certificate lifetime and re-issuance
- Not supported
- Exit codes
- Sessions and administrator termination
- Troubleshooting
- Hub configuration
Veyport's SSH gateway lets an operator connect to a fleet server through the Hub instead of connecting to the node's own SSH daemon. Interactive vey ssh <server> opens a PTY; the feature-branch command form vey ssh <server> -- '<command>' submits one command without a PTY. The Hub authenticates the request, rechecks live authorization, prepares the target OS identity, and brokers execution over the enrolled agent's outbound gRPC stream.
That means:
- The Hub is on the execution path and records command lifecycle metadata. Under Enhanced, a command starts only after its intent is preserved by the external receipt authority and encrypted archive.
- Interactive PTY and no-PTY execution use separate protocol paths; command streams have bounded buffering and explicit results.
- Authorization and target identity are checked live. A valid SSH certificate does not grant terminal or command authority by itself.
The currently published command-passthrough prototype 9 includes Hub, CLI, and agent artifacts. CLI prototype 6 is also compatible with the prototype 9 Hub. These prototype builds are not a stable or main-branch release. Check current publication and qualification on the release page and verify target agent support. This publication information is not new runtime qualification evidence. See CLI Automation for script examples and outcome handling. Standard command execution has no external audit-service dependency. Enhanced is opt-in and requires a qualified external receipt/archive configuration.

-
veyinstalled and a hub configured — quickest:curl -fsSL https://<your-hub>/install/cli.sh | sh, thenvey --hub https://<your-hub> login(see CLI for details and manual install). - An interactive
vey loginsession. Certificate issuance is refused to API-token credentials — see FR-002/SC-004 below. - A native
sshclient on your machine (OpenSSH or compatible). There is no bundled SSH client —vey sshexecs your system'sssh. - The gateway must be enabled and reachable on the Hub you're connecting to (see Hub configuration and Troubleshooting).
- Linux and macOS clients. Windows clients are not supported in this release (see Not supported).
vey --hub https://hub.example.com ssh-certThis provisions or refreshes your SSH credential:
- Generates an ed25519 keypair locally if you don't already have one stored for this hub (re-running reuses the existing keypair — only the certificate is refreshed).
- Fetches the gateway's host key (
GET /api/ssh/host-key) and caches its fingerprint for pinning. - Requests a signed user certificate (
POST /api/ssh/certificates), passing only your public key — the Hub derives the principal (your username) from your authenticated session; you cannot request a different identity. - Stores the private key, certificate, expiry, and host fingerprint alongside your existing
veycredentials (same at-rest protection as your login session: OS keyring, or a0600file fallback). - Prints the resulting principal, expiry, gateway port, and host-key fingerprint.
| Property | Value |
|---|---|
| Auth required | Interactive vey login session only — an API token is refused with exit 3 before any network call |
| Rate limit | 10 issuance requests per minute per caller (429 → exit 7) |
--json shape |
{principal, expires_at, host_key_fingerprint, gateway_port} |
| Idempotent | Yes — re-running refreshes the certificate and keeps the keypair |
Why interactive-only? Certificate issuance requires an operator's interactive login. The Hub enforces this server-side (interactiveOnly middleware); vey ssh-cert also refuses API-token credentials before making a request. Once issued, a certificate may be used by a delegated script. It remains the operator's credential, and an API token alone cannot obtain or renew it. See CLI Automation for the supported workflow and its limits.
$ vey --hub https://hub.example.com ssh-cert
Signed cert for alice, valid until 2026-08-06T20:14:03Z
Gateway port: 2222
Host key fingerprint: SHA256:AbCdEf...vey --hub https://hub.example.com ssh web01vey ssh is a thin, safe wrapper around your native ssh client:
- Checks that you have a stored, unexpired certificate; if not, it explains and points you to
vey ssh-cert. - Resolves
<server>the same wayvey servers getdoes — a unique name or ID; an ambiguous name lists the candidates; an unknown server is a clean not-found error. - Fetches the gateway's current public host key over the authenticated Hub API and builds a temporary host-verification file from that key. The fingerprint saved by
vey ssh-certis used to warn if it changed; it is not the source of the pinned key. - Execs your operator's actual
sshbinary with the certificate, the resolveduser+serverlogin name, and pinned host verification — it never edits your~/.ssh/config. - Propagates the
sshclient's exit status back to your shell.
You get a full interactive PTY: full-screen editors, pagers, htop, and window resizing all work, because the Hub relays raw terminal bytes and window-change events end-to-end over the same channel the web terminal uses.
vey ssh web01 -- 'uname -a'
# Standard profile only: Enhanced rejects arbitrary nonempty stdin.
printf 'hello\n' | vey ssh web01 -- 'cat'Everything after -- must be exactly one command-string argument. The CLI invokes
OpenSSH directly; it does not execute the command through a local shell or join
multiple arguments. It disables PTY allocation and interactive authentication.
Without redirected input, stdin is EOF. Standard mode streams redirected stdin.
In Enhanced mode the hub collects all stdin before preparation and admission.
Only exact bash -s, /bin/bash -s, sh -s, or /bin/sh -s forms accept
nonempty stdin. The whole script is limited to 16 MiB and 120 seconds, and is
verified before any process starts. Other Enhanced commands must receive empty
stdin. Script arguments and mixed script/data input are not supported. Standard
accepts arbitrary streamed stdin.
Stdout and stderr are separate binary-safe streams; their individual order is
preserved, with no promised total ordering between the two. Remote exit status is
propagated through SSH. Exit 255 can overlap a remote exit with a native SSH
failure. Do not automatically retry after a connection loss or uncertain result.
In Standard mode, preparation, admission, and execution transport failures return
a diagnostic on stderr and a nonzero status to a responsive client. Enhanced
mode provides this response after complete input collection; cancellation during
collection may instead close the channel. Once finalization begins for an
accepted execution, final SSH response delivery and relay cleanup have a deadline
of at most two seconds. The
Hub closes the SSH connection if response delivery stalls; this also ends other
channels sharing that connection.
Browser refresh does not cancel a command. Cancellation and live authority
revocation are checked by the Hub's execution lifecycle, including immediately
before Start is sent; they cannot recall a start already sent or guarantee
that a process already starting has stopped. Remote output is not archived as
command evidence.
Enhanced evidence covers the exact submitted command or complete supported script, not every internal shell instruction, downloaded program, child-process action, or remote output. Command execution is not a sandbox and does not provide full host process telemetry.
Your Veyport username authenticates you to the gateway. It does not automatically become the Linux username on the target VM. Both SSH and browser terminal sessions use the same authorization decision and agent PTY manager:
-
Local admins inherit the agent's OS account. The standard installer runs the
agent as root, so
whoamiin the shell reportsroot. This is the current design. - LDAP admins and authorized LDAP users run as their mapped LDAP username on the target host. The host must resolve that account; lookup failure refuses the shell rather than falling back to root.
vey ssh has no option to select an arbitrary Linux user. A root (/) path
assignment grants terminal eligibility to an authorized LDAP user; it does not
restrict commands inside the shell to a filesystem subtree. The shell follows its
OS account's permissions. See Architecture for the execution identity table.
vey ssh is a convenience wrapper — everything it does can also be done with a plain ssh command, which is useful for scripting or when vey isn't installed on the machine you're connecting from:
ssh -p 2222 alice+web01@hub.example.comThe SSH gateway addresses both who you are and which server you want in a single login name, since the SSH protocol only carries one username per connection and the gateway does not support ProxyJump. The login name is split on the first +:
| Part | Meaning |
|---|---|
Left of the first +
|
Your veyport username. It must equal a principal on the certificate you present — the gateway checks cert principal == left part. |
Right of the first +
|
The target server's name or ID, resolved the same way vey servers get resolves it. |
Because the split is on the first +, a server name may safely contain a +, but a veyport username may not — a username containing + cannot be split unambiguously and is not supported for SSH access. Affected users must use the web terminal instead. A login name with no + at all is rejected with an explanatory SSH banner pointing back at this format.
vey ssh web01 is exactly equivalent to constructing this manual form using your cached certificate and host key.
The gateway presents a stable ed25519 host key that survives Hub restarts and upgrades — it is never silently regenerated. If the stored host key ever becomes unusable (corrupted, undecryptable), the gateway disables its listener entirely rather than serve a changed identity; the rest of the Hub keeps running.
Fetch the current fingerprint at any time:
curl -H "Authorization: Bearer <token>" https://hub.example.com/api/ssh/host-keyor read it back from your last vey ssh-cert run:
vey --json --hub https://hub.example.com ssh-cert | jq -r .host_key_fingerprintvey ssh-cert caches this fingerprint. Each vey ssh connection fetches the current public host key over the Hub API and pins native SSH to that returned key in a temporary known-hosts file; it compares the key's fingerprint with the saved value and warns if it changed. The saved fingerprint detects rotation but does not supply the pinned key. This avoids a blind trust-on-first-use prompt. If you connect manually, verify the fingerprint through a trusted channel and pin it in your client's own known_hosts instead of accepting on trust.
Certificates are short-lived — ~12 hours by default, tunable per hub (see Hub configuration). A few properties worth knowing:
- The certificate binds a single principal: your username, and only your username. The Hub derives it from your authenticated session; there is no way to request a certificate for someone else.
- There is no revocation mechanism for issued certificates in this release. The short TTL plus fresh, per-connection authorization against live account/permission state is the accepted mitigation — see the engineering security model for the full rationale.
- A certificate expiring mid-session does not drop that session; only new connection attempts are re-checked against the certificate's validity, which matches standard SSH behavior.
- Re-run
vey ssh-certany time to refresh — it keeps your existing keypair and simply asks the Hub for a new certificate. There's no reason not to re-run it liberally; it's cheap and rate-limited generously (10/min).
The gateway does not provide these features:
| Capability | Behavior |
|---|---|
scp / sftp
|
Refused — the subsystem channel request is rejected |
Port forwarding (-L / -R / dynamic -D) |
Refused — forwarding channel/global requests are rejected |
SSH agent forwarding (-A) |
Refused — auth-agent-req is rejected |
| Windows clients | Not tested or supported — use the web terminal instead |
Command mode accepts one SSH exec request per channel. PTY requests, arbitrary environment variables, subsystems, forwarding, duplicate commands, and malformed or oversized requests are refused. This is not a general-purpose remote SSH server.
If you need any of these, use the existing Server Detail file browser/log tailer for file access, or the browser terminal for anything the interactive shell can't cover.
vey ssh-cert and vey ssh use the same stable exit-code taxonomy as the rest of vey (see CLI for the full table):
| Code | Meaning for SSH commands |
|---|---|
0 |
Success |
1 |
Local ssh client could not start, or a local I/O/process error occurred |
2 |
Usage error, or an ambiguous server name for vey ssh <server>
|
3 |
Auth failure — no/expired stored certificate or a Hub authentication refusal |
5 |
Unknown server for vey ssh <server>
|
6 |
Connectivity failure during Hub API preflight; a gateway connection failure is returned by native SSH (often 255) |
7 |
Rate-limited (429) on certificate issuance |
| (varies) |
vey ssh propagates whatever exit status your native ssh client returns once a session is actually open |
As of feature 009, an open SSH gateway shell shows up alongside web and CLI sessions in the Sessions views described in Settings: your own shells appear in "Your sessions" on the Profile tab, and an admin sees any user's open shells (kind SSH shell, with the target server, when it started, and when it was last active) in that user's Sessions panel on the Users tab.
An administrator can terminate a shell directly from that panel, and two broader actions close it along with everything else: Log out everywhere (admin) and Sign out other sessions (the shell's own owner). Disabling the account also closes it. In every case the shell closes within a few seconds and the client sees an explanatory message on stderr before the connection ends:
veyport: session ended by an administrator
(or veyport: account disabled, veyport: signed out everywhere, depending on why), followed by
exit status 1. This is audited as ssh.session_closed with detail reason=forced - see
Audit Logs.
Open shells are not subject to the idle or absolute session timers that apply to web and CLI sessions (see Logging In). Certificate expiry limits new SSH connections; it does not end an already-open shell. Shells remain subject to live account/access revocation and explicit termination as described here. No web or CLI idle timer closes an open SSH shell.
| Symptom | Likely cause | Fix |
|---|---|---|
vey ssh says your certificate is missing or expired |
No certificate issued yet, or the ~12h TTL has passed | Run vey ssh-cert
|
permission denied after a successful cert handshake |
AuthorizeTerminalExecution denied you for this server right now — no terminal permission, no root (/) assignment, unknown server, or your account is disabled |
Check your permissions on Settings / Server Detail; this is re-evaluated live and is intentionally not implied by having a valid certificate |
veyport: the SSH username must be <veyport-user>+<server> banner |
You connected without the +server suffix, or with a raw ssh command missing it |
Use vey ssh <server>, or the manual form ssh -p 2222 user+server@hub
|
| Connection refused / times out on port 2222 | The gateway is disabled, or the port is blocked by a firewall/proxy in front of the Hub | Confirm ssh_gateway_enabled and the reachability of the configured port; SSH is a raw TCP protocol and typically needs its own reverse-proxy route (a TLS-terminating HTTP proxy like Traefik does not carry it) |
vey ssh-cert fails with an interactive-login error even though you're logged in |
Your active credential is an API token (VEYPORT_TOKEN or a config api_token), which always wins over a stored interactive session |
Unset the token for this invocation, or run vey login and don't set VEYPORT_TOKEN
|
| "unknown server" / "ambiguous server name" / "server unavailable" | Target resolution failed — the name/ID doesn't exist, matches more than one server, or the target agent isn't currently connected |
vey servers list / vey servers get <server> to check the name and online status |
| Host key changed warning | Should not happen — the gateway's host key is stable across restarts. If it does happen, treat it as a genuine warning and verify out-of-band before trusting the new key | Compare against GET /api/ssh/host-key from a trusted channel; do not just accept |
The gateway's port, whether it's enabled, and certificate TTL are administrator-tunable — there is no end-user flag for any of these:
| Setting |
_config key |
Default | Notes |
|---|---|---|---|
| Gateway enabled | ssh_gateway_enabled |
true |
When false, the port is never opened, and POST /api/ssh/certificates returns a clear "gateway disabled" error instead of an unusable certificate |
| Listen address | ssh_addr |
:2222 |
Overridable per-run via the Hub's --ssh-addr flag; the _config value wins over the flag when both are set |
| Certificate TTL | ssh_cert_ttl_hours |
12 |
Hours; applies to every certificate issued after the change, not retroactively |
An invalid or unparseable stored value falls back to the next level (DB → flag → hardcoded default) rather than failing Hub startup.
Related: CLI for the full vey command reference and exit-code taxonomy, API Reference for the /api/ssh/* and session endpoint contracts, Server Detail for the browser-based terminal alternative, Settings for viewing and terminating sessions and shells, Audit Logs for reviewing ssh.* events.
Certificates carry a signed immutable account identity as well as the username
and SSH credential generation. Deleting and recreating a username cannot restore
an old certificate. After upgrading to this format, sign in with MFA and run
vey ssh-cert to replace identity-less certificates. Each shell and exec channel
checks the identity again, including channels opened on an older connection.
Established interactive shells also revalidate policy, continuity, and account
access through SQLite every second. Forced closure drops queued output; a blocked
SSH output write may close the entire SSH connection to stop forwarding.
Veyport User Guide
Getting Started
Using Veyport
Administration
Setup & Deployment
- Architecture
- Deployment
- Deployment On-Prem
- Deployment AWS
- Deployment Azure
- Deployment GCP
- Proxy Configuration
- Development
- API Reference
Help