Cubicle

SSH into a Box

Open an SSH session to a Box with cubicle ssh, or write an OpenSSH alias with cubicle ssh-config for ssh, file copies and VS Code or Cursor Remote-SSH, using ed25519 keys, Box grants and pinned host keys.

SSH gives you an interactive shell in a Box as your own guest account, over Cubicle's private network. The cubicle CLI connects the network, writes an OpenSSH alias for the Box and pins its host key, so plain ssh, scp, rsync and editor remote extensions work with it. SSH needs an interactive login: service accounts and API keys cannot use SSH, so automation runs commands with exec instead.

Quick start

Run these three commands once the CLI is installed and signed in:

  1. Install the tunnel, the network helper that SSH needs. It asks for confirmation and runs sudo.

    cubicle tunnel install
  2. Register your SSH public key. Without an argument the CLI reads ~/.ssh/id_ed25519.pub.

    cubicle keys add
    # added SHA256:…
  3. Connect. The Box must be running and you must hold a grant on it (the Box's creator gets one automatically).

    cubicle ssh acme/job-1

The rest of this page explains each step and the options.

Before you begin

  • The cubicle CLI on macOS or Linux, signed in with cubicle login. See Install the CLI and Sign in and API keys.
  • The tunnel, which has its own requirements on Linux and macOS. See platform requirements.
  • An ed25519 key pair. If ~/.ssh/id_ed25519 does not exist, create it with ssh-keygen -t ed25519.
  • Active membership in the Box's organization and an explicit grant on the Box. The owner and admin roles alone are not enough; see Grant access to a Box.
  • A running Box. Refer to it by ORG/NAME (acme/job-1) or by its ID. The CLI does not accept a bare name.

How it works

cubicle ssh and the alias connect straight to the Box's private address on port 22, through the WireGuard tunnel to Gateway. Nothing is exposed on the public internet. See Networking for the full path and what else the tunnel carries.

SSH works only when two sides are ready:

  • The Box: running at its current generation, on a healthy Node, with its host key published. ssh.ready on the Box reports this part.
  • You: an active member of the Box's organization with a grant on it and at least one active ed25519 key, and the Box's Node has applied your guest account and keys at the Box's current generation. This part shows up as the not ready reason that cubicle ssh-config prints (see Troubleshooting).

Add an SSH key

Cubicle accepts only ssh-ed25519 public keys. RSA, ECDSA and security-key types such as sk-ssh-ed25519@openssh.com are refused with invalid_key: only ssh-ed25519 public keys are accepted. Keys belong to your account, not to one Box: adding or revoking a key updates your guest account on every Box you are granted.

  1. Add the key. Pass a path to use a key other than ~/.ssh/id_ed25519.pub.

    cubicle keys add ~/.ssh/id_ed25519.pub
    # added SHA256:…

    In the Dashboard, open Account, paste the public key into My SSH keys and click Add key.

  2. Check that it is active.

    cubicle keys list
    # 5d2c8f41-9a7e-4b3c-8d1f-2e6a0b9c7d5e  SHA256:…  dev@laptop

Remove a key with cubicle keys rm KEY_ID. Removing a key stops new SSH logins with it. It does not cut your network access to the Box.

Grant access to a Box

The user who creates a Box gets a grant on it automatically; anyone else, owners and admins included, needs one from an owner or admin of the Box's organization. Grant access to a Box in the members guide shows how to find the user ID, add the grant and check that it has reached the Box.

cubicle ssh and cubicle ssh-config

Both commands write the same OpenSSH configuration. They differ in what else they do.

CommandConnects the tunnelWrites the aliasRuns ssh
cubicle ssh acme/job-1YesYesYes
cubicle ssh-config acme/job-1NoYesNo

cubicle ssh exits with ssh's exit code. Pass arguments for ssh after --, for example a remote command:

cubicle ssh acme/job-1 -- uname -a

cubicle ssh-config prints the alias to use. If you are not ready yet, it adds the reason:

cubicle ssh-config acme/job-1
# configured: ssh cubicle-acme-job-1

An alias written by ssh-config needs the tunnel to be connected. Run cubicle connect first, or run cubicle ssh once; the connection stays up after ssh exits. See Reach a Box over the private network.

Both commands take the same options, --alias, --identity and --accept-new-host-key, before any --; see cubicle ssh-config. Pass --identity a full path, because the path is written into the alias as given.

What the CLI writes

The CLI writes to ~/.ssh (CB_CLI_SSH_DIR changes the directory):

FileContents
~/.ssh/cubicle_configOne Host block per alias, between # BEGIN cubicle <alias> and # END cubicle <alias>.
~/.ssh/cubicle_known_hostsOne pinned host key per Box, stored under the name jevbox-<box ID>.
~/.ssh/configAn Include line for cubicle_config, added once at the top so it applies to every Host.

Running the command again replaces the alias's block. The block for acme/job-1 looks like this:

# BEGIN cubicle cubicle-acme-job-1
Host cubicle-acme-job-1
    HostName job-1.acme.cubicle.internal
    User jv_6b9e0d2c41af
    IdentityFile /home/you/.ssh/id_ed25519
    IdentitiesOnly yes
    HostKeyAlias jevbox-<box ID>
    UserKnownHostsFile /home/you/.ssh/cubicle_known_hosts
    StrictHostKeyChecking yes
    ForwardAgent no
    ForwardX11 no
    ServerAliveInterval 5
    ServerAliveCountMax 2
# END cubicle cubicle-acme-job-1

HostName is the Box's private DNS name, <box>.<org>.cubicle.internal. The CLI has no command to remove an alias; delete its block from ~/.ssh/cubicle_config by hand.

jv_ in guest account names and jevbox- in pinned host key names are legacy prefixes.

Use the alias with other tools

Every tool that reads your OpenSSH configuration can use the alias while the tunnel is connected:

cubicle connect
ssh cubicle-acme-job-1

To copy files with scp, sftp or rsync, see Copy files over SSH.

VS Code and Cursor. Run cubicle connect first. Then, in the Remote-SSH extension, run Remote-SSH: Connect to Host… and enter the alias, cubicle-acme-job-1. The editor uses the same ~/.ssh/config, so the pinned host key and identity apply.

Forwarding a Box-local port. OpenSSH local forwarding to the Box's own localhost works, which is what editor remote extensions rely on:

ssh -N -L 8080:localhost:8080 cubicle-acme-job-1

Forwarding to other hosts, remote forwarding (-R), agent forwarding and X11 forwarding are disabled in the Box. To reach a service that listens on the Box's network interface, use the private network directly.

The guest account and sudo

  • You log in as your own guest account, jv_ followed by the first 12 hex characters of your user ID (for example jv_6b9e0d2c41af), which also runs your exec commands. cubicle grants list shows it, and cubicle ssh-config writes it as the alias's User.
  • The account has passwordless sudo, so your SSH sessions have root inside the Box. The desktop console signs in as a different account.

Guest accounts covers its home directory and shell, and what happens to it while you have no active SSH key.

Host keys

Cubicle generates each Box's SSH host key when it provisions the Box, and the CLI pins the key the API publishes in ~/.ssh/cubicle_known_hosts with StrictHostKeyChecking yes, so ssh never asks you to trust a key and never silently accepts a new one. Pins are per Box ID, so a new Box that reuses a deleted Box's name gets its own pin without a warning, while a changed key for the same Box stops the connection with one of the two host key errors under Troubleshooting; see SSH and host-key pinning.

Keepalives and disconnects

The alias sets ServerAliveInterval 5 and ServerAliveCountMax 2. When the Box stops answering, ssh gives up within about 15 seconds with Timeout, server … not responding. instead of hanging.

When access ends

Your session ends, or new sessions are refused, when any of these happens:

EventEffect
The Box hibernates, by request or by itselfThe VM stops, so open connections end. See automatic hibernation.
The Box is stopped or deletedThe VM stops, so open connections end.
The Box is started again while runningSSH traffic cannot reach the Box until its Node has applied the new generation, and open connections may not survive the gap.
Your grant or membership is revokedNetwork access ends within the 30 s lease, including open sessions, and your guest account loses its keys.
Your device is revoked, or you log outThe tunnel closes, so open sessions drop.
Your CLI login expires (after 30 days)The tunnel can no longer refresh and closes. Run cubicle login, then cubicle connect.
You remove an SSH keyNew logins with that key fail. Network access is unchanged.

Verify

Run a command and check the account it runs as:

cubicle ssh acme/job-1 -- whoami
# jv_6b9e0d2c41af

cubicle boxes list --org acme shows ssh=ready for a Box that is SSH-ready.

Troubleshooting

Troubleshooting covers errors shared with other pages. These are the SSH errors and their fixes.

box has no trusted host key or guest account yet (<reason>); retry when it is running

The Box is not running at its current generation, or you have no guest account on it yet. The reason in parentheses says which. Start the Box with cubicle boxes start acme/job-1 --wait, or ask for a grant if the reason is grant_inactive.

configured: ssh cubicle-acme-job-1 (not ready: <reason>)

The alias was written, but SSH will fail until the reason clears. Reasons about the Box itself, which cubicle boxes list also shows as ssh=<reason>, are explained with what to do in When a Box is ready. These five concern you rather than the Box, so only cubicle ssh-config and the Access tab of the Box's details in the Dashboard show them:

ReasonDashboard wordingFix
user_disabledYour account is disabledAsk a Cubicle operator.
membership_inactiveYou are not an active member of its organizationAsk an owner or admin to add you.
grant_inactiveYou have no active grant on this BoxAsk an owner or admin for a grant.
no_active_keyYou have no active SSH key (add one under Account)cubicle keys add
authorization_pendingGuest accounts and keys are still being appliedThe Node is still applying your account and keys. Retry in a few seconds.

Permission denied (publickey).

The Box refused your key. Run cubicle ssh-config acme/job-1 and read the not ready reason. If there is none, check that --identity points at the private key whose public half you added with cubicle keys add.

ssh: Could not resolve hostname …

The tunnel is not connected, the Box is not routed, you have not run cubicle ssh-config for the Box, or your network blocks the tunnel's outbound UDP traffic to Gateway. Run cubicle connect and check that the Box appears in cubicle status. Stopped Boxes, and Boxes you have no grant on, do not resolve. A Box that became ready after you connected is added on the next refresh, within about 10 seconds.

the host key for … changed

The full message is the host key for jevbox-<box ID> changed. Verify the box was restored or replaced, then rerun with --accept-new-host-key, where jevbox-<box ID> is the name of the Box's pinned host key. The API now publishes a different host key for this Box than the one the CLI pinned in ~/.ssh/cubicle_known_hosts. Confirm the change is expected, then accept the new key:

cubicle ssh-config acme/job-1 --accept-new-host-key

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

OpenSSH refused the connection because the key the Box's SSH server presented differs from the pinned one. Do not turn off host key checking. Compare the pin with the key the API publishes:

cubicle ssh-config acme/job-1

If it does not report a changed host key, your pin matches the key the API publishes, so the host key files under /etc/ssh inside the Box no longer match the key Cubicle generated, for example because they were regenerated in the Box.

Errors fixed on other pages

  • the Cubicle tunnel is not installed; run `cubicle tunnel install`, or the helper is not responding: see the private network guide.
  • api_key_not_allowed: API keys cannot use this route: CB_CLI_API_KEY is set, and SSH needs an interactive login. Unset it and run cubicle login; see API keys.

On this page