Skip to content

SSH

The on-box examples on this page were captured 2026-07-28. The command catalog was refreshed 2026-08-03 after the from-zero rebuild and records the current node and software build.

SSH reaches the appliance admin account, whose login shell is the Site CLI (/opt/bin/vpmu). This is the only route to the full on-box command surface — the debug API exposes 43 commands, and the appliance itself offers many more that are not on that API at all.

Terminal window
cd terraform
SLI=$(terraform output -json ce_sli_private_ips | jq -r '.eastus01')
ssh -tt -i ~/.ssh/id_ed25519 -J azureuser@<operator-vm> "admin@$SLI"

Every part of that command line is load-bearing, and the next three sections explain which failure each one prevents.

There is no shell prompt to land on: admin’s login shell is the Site CLI, so you arrive at its >>> prompt. Run on-box commands as execcli <name> there — see on-box commands.

Why cloud-init writes the key, and not the API

Section titled “Why cloud-init writes the key, and not the API”

admin_user_credentials.ssh_key on the site object looks exactly like the field for this. It is accepted, it survives a read-back, and it configures nothing. vpm owns the node’s local users and declines to touch this one — from the node’s own log, three times over a single boot:

vpm users.go:165: Won't do any change for user admin (internal skip)

Everything else follows from that one line. /var/home/admin/.ssh never exists, before or after the site reaches ONLINE. The admin shadow last-change date stays at the image build date while vesbkp and vesopcon show the current date, because vpm set those two and skipped admin. And vpm logs nothing about ssh_key or authorized_keys at any point.

So the file has to be written outside vpm. admin is uid 2202, baked into the node image, so it exists before cloud-init runs and owner: admin:admin resolves at write time — no runcmd, no ownership fix-up:

- path: /var/home/admin/.ssh/authorized_keys
permissions: "0600"
owner: admin:admin
content: |
${ssh_public_key}

sshd was always willing. sshd -T reports pubkeyauthentication yes, and admin appears in AllowUsers in both sshd_config files that ship on the node. There was never anything to enable — only a missing file.

sshd binds 0.0.0.0:22, but only the internal (SLI) address is on the host network stack in a way sshd will answer on. eth0 is renamed a-i-eth0 and carries no host IP at all — the Argo data plane owns that interface, and the SLO address it would have carried appears on vhost0 instead. The other two NICs stay on the host stack under their own names.

Probed from a VM inside the VNet, against one CE:

SLO address timed out
external address timed out
internal/SLI address OPEN SSH-2.0-OpenSSH_8.7
an unused address timed out (control)

A probe against the address you know the node by therefore returns exactly what a closed security group returns. Nothing is blocking it; there is no listener on that address.

The CE public addresses have no listener on 22 either, which is why the command above goes through -J: an operator VM inside the VNet, in the same subnet as the SLI addresses. This deployment builds one for the purpose — terraform output -raw client_vm_name names it.

The Site CLI needs a terminal, and a carriage return

Section titled “The Site CLI needs a terminal, and a carriage return”

The admin login shell is not a shell. It is a go-prompt application, which puts the terminal into raw mode and reads keystrokes rather than lines. Four consequences, each of which fails in a way that looks like a different problem:

MechanicWhat happens without it
Allocate a terminal (ssh -tt)panic: no such device or address from go-prompt.NewStandardInputParser, which reads as a crashed appliance
Send a carriage return, not a line feed, for EnterThe line is never submitted, and the session closes on end-of-input having printed nothing
Keep standard input openThe connection ends before the command has rendered, so a working command looks silent
Write the command text and the Enter byte separatelyThe newline lands in the buffer as a literal character and the CLI answers unknown command, which reads as though the command does not exist

ssh host 'some-command' therefore does not work: the argument is ignored and the interactive prompt starts anyway. Drive it as a terminal or not at all. scripts/sitecli_ssh_harvest.py is the reference implementation.

Before you type anything, the login banner has already answered several of the questions you would otherwise spend commands on. From f5-xc-ce-vm-01, ASCII art and the public IP elided:

UNAUTHORIZED ACCESS TO THIS DEVICE IS PROHIBITED
All actions performed on this device are audited
Using https://register.ves.volterra.io
OS: rhel-9.2024.6
Memory: 32768MiB
Storage: sda: 31GiB
CPU: Model: Intel(R) Xeon(R) Platinum 8272CL CPU @ 2.60GHz | CPUs: 8
Software: crt-20250613-3382
DNS: 168.63.129.16: OK
NTP: Synced
Uptime: 0 days, 5 hours, 20 minutes
Registration Status: PROVISIONED
SLO IP: 10.0.1.4/26
WELCOME IN SITE CLI

Registration Status distinguishes a node still coming up (PROVISIONING) from one that is done (PROVISIONED). Software is the build string that decides which commands exist. DNS and NTP cover the two dependencies that break registration first, so a node that never came online has usually already told you why here — before chronyc-sources or dig are needed at all.

  1. Generate a keypair, if you do not already have one. Ed25519 rather than RSA: shorter, and accepted by the appliance sshd.

    Terminal window
    ssh-keygen -t ed25519 -C "ce-operator" -f ~/.ssh/id_ed25519
  2. Point the deployment at the public half. The root module reads the file once and passes the string down to every node:

    terraform/terraform.tfvars
    ssh_public_key_path = "~/.ssh/id_ed25519.pub"

    ssh_public_key takes the material inline instead, which is what the plan tests use. The private half never leaves your workstation.

  3. Apply. Read the warning at the top of this page first — on an existing deployment this replaces the CE VMs.

Each of these was tested and ruled out while the route was still believed impossible. Each is plausible enough to cost a day.

Not the causeHow it was ruled out
The credential only applies at create timesshd starts about 90 seconds into first boot, before the site object exists. admin_user_credentials is also present in ReplaceSpecType and applies in place.
block_all_servicesThe port-22 closure on the management address is identical whether services are blocked or not.
A trailing newline on the keyTested both ways, no change. Cloud-init still strips it, because a literal block would otherwise render a second, empty line.
Pinned, older CE softwareReproduced on three builds, including one running OpenSSH 9.9.
A missing admin_passwordSetting admin_password alongside ssh_key on a from-scratch CE changed nothing.