Deploy through a jump host
A deployment target does not need a public SSH port. Name the bastion it is reached through, and Onebox tunnels the connection through it:
environments: production:jump sits beside server and takes the same two forms it does — the one-line
user@host above, or an object when you want to be explicit:
environments: production: jump: host: bastion.example.com user: deploy port: 2222The user and port are optional in both forms: port 22 is used when you name
none, and $USER supplies the user — ob does not read ~/.ssh/config, so a
User or ProxyJump written there has no effect on it. An environment with no
jump connects directly, exactly as before.
An IPv6 bastion is bracketed in the one-line form, where the brackets are what
separate the address from the port, and bare in the object form, where host
and port are already separate fields. Brackets carried over into host are
stripped rather than refused:
jump: "deploy@[2001:db8::1]:2222" # or jump: { host: "2001:db8::1", user: deploy, port: 2222 }Nothing is deployed to the jump host. It forwards one TCP connection to the server and runs no commands, holds no releases, and needs no Docker. Onebox remains one application on one host; the bastion is only how that host is reached.
What Onebox verifies
Section titled “What Onebox verifies”Both hops are verified and authenticated separately:
- The bastion’s host key is checked against
known_hosts. - Onebox authenticates to the bastion.
- The bastion opens a forwarded connection to the server.
- The server’s host key is checked against
known_hosts, independently. - Onebox authenticates to the server.
A trusted bastion does not vouch for the server. Trusting one is not trusting the other, and neither key is ever accepted implicitly.
The SSH agent is never forwarded. Your local agent may sign for either hop, but its socket is not exposed to the bastion, so a compromised jump host cannot borrow your identity to reach anything else.
Exactly one hop is supported. There is no ProxyCommand, and Onebox does not
read ~/.ssh/config — a ProxyJump there has no effect on ob, because the
transport dials and verifies on its own rather than shelling out to ssh.
Enrolling both host keys
Section titled “Enrolling both host keys”Because each hop is verified, known_hosts needs an entry for each. The
bastion is reachable from your machine:
ssh-keyscan -H bastion.example.com >> ~/.ssh/known_hostsThe server is not, so scan it from the bastion and append the result locally:
Read that second key the way you would any key you did not fetch yourself: it arrives over a connection the bastion mediates, so it is only as trustworthy as the bastion at the moment you enrolled it. Comparing it against the key printed on the server’s own console is the stronger move where you can.
If the server listens on a non-default port, known_hosts must be keyed with
it — ssh-keyscan -p 2222 writes the [host]:port form Onebox looks up.
The route is part of the plan
Section titled “The route is part of the plan”Plans and approvals name the whole route, not just the server:
target [email protected] via [email protected]:2222Changing the bastion changes what you approved, so an approval issued for one route does not execute against another. Confirm the route on that line before approving, exactly as you would the server.
Reading a failure
Section titled “Reading a failure”Errors name the hop and the stage, so a failure points at one thing to fix:
| Error begins | What failed |
|---|---|
jump ssh …: (no stage) | The bastion could not be resolved, connected to, or finished a handshake with — including a timeout |
jump ssh …: host key: | The bastion’s key is missing from, or disagrees with, known_hosts |
jump ssh …: authenticate: | The bastion refused your key |
target ssh …: not reachable from the jump host: | The bastion connected but could not reach the server — wrong private address, or the bastion’s own policy forbids the forward |
target ssh …: host key: | The server’s key is missing or mismatched, even though the bastion is trusted |
target ssh …: authenticate: | The server refused your key |
A failure that names no stage is a transport problem — a timeout, a cancelled command, or a peer that hung up — never a rejected key.
The distinction between the last four is the one worth internalising: a working bastion tells you nothing about the server, and Onebox will not let a trusted first hop paper over a problem with the second.
Local hooks
Section titled “Local hooks”A hook declared local: true runs on your machine, which has no tunnel of its
own. Such a hook receives ONEBOX_SSH_JUMP alongside ONEBOX_SERVER, empty when the
connection is direct:
ssh ${ONEBOX_SSH_JUMP:+-J "$ONEBOX_SSH_JUMP"} "$ONEBOX_SERVER" -p "$ONEBOX_SSH_PORT" 'uptime'A local hook written before you added a bastion will otherwise try to reach a server it can no longer see. The rest of the namespace is listed under Environment variables.