- Home
- Multi-Cloud Networking
- Customer Edge diagnostics
- Reaching a Customer Edge
- SSH
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.
cd terraformSLI=$(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.
Why it answers on one address only
Section titled “Why it answers on one address only”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 outexternal address timed outinternal/SLI address OPEN SSH-2.0-OpenSSH_8.7an 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:
| Mechanic | What 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 Enter | The line is never submitted, and the session closes on end-of-input having printed nothing |
| Keep standard input open | The connection ends before the command has rendered, so a working command looks silent |
| Write the command text and the Enter byte separately | The 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.
The banner is a free health check
Section titled “The banner is a free health check”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 PROHIBITEDAll actions performed on this device are audited
Using https://register.ves.volterra.ioOS: rhel-9.2024.6Memory: 32768MiBStorage: sda: 31GiBCPU: Model: Intel(R) Xeon(R) Platinum 8272CL CPU @ 2.60GHz | CPUs: 8Software: crt-20250613-3382DNS: 168.63.129.16: OKNTP: SyncedUptime: 0 days, 5 hours, 20 minutesRegistration Status: PROVISIONEDSLO IP: 10.0.1.4/26WELCOME IN SITE CLIRegistration 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.
Supplying the keypair
Section titled “Supplying the keypair”-
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 -
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_keytakes the material inline instead, which is what the plan tests use. The private half never leaves your workstation. -
Apply. Read the warning at the top of this page first — on an existing deployment this replaces the CE VMs.
What this is not
Section titled “What this is not”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 cause | How it was ruled out |
|---|---|
| The credential only applies at create time | sshd 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_services | The port-22 closure on the management address is identical whether services are blocked or not. |
| A trailing newline on the key | Tested both ways, no change. Cloud-init still strips it, because a literal block would otherwise render a second, empty line. |
| Pinned, older CE software | Reproduced on three builds, including one running OpenSSH 9.9. |
A missing admin_password | Setting admin_password alongside ssh_key on a from-scratch CE changed nothing. |
The other routes
Section titled “The other routes”ONLINE.