SSH Gateway & Tunnel
Secure remote access to leased VMs
The SSH gateway provides transparent remote access to leased VMs. Consumers connect via standard SSH, and the gateway routes their session through the libp2p network to the provider's VM — no direct network connectivity to the VM is required.
Architecture
Consumer Machine Gateway Node Provider Node VM
│ │ │ │
│ SSH connect │ │ │
│ user=<leaseHash> │ │ │
│───────────────────────►│ │ │
│ │ auth: verify │ │
│ │ AccessPubKey │ │
│ │ │ │
│ │ libp2p stream │ │
│ │ /xe/tunnel/2.0.0 │ │
│ │──────────────────────►│ │
│ │ │ validate │
│ │ │ lease │
│ │ │ │
│ │ │ DialSSH() │
│ │ │──────────────►│
│ │ │ TCP to │
│ │ │ 127.0.0.1: │
│ │ │ {sshPort} │
│ │ │ │
│◄───────────────────────┼───────────────────────┼───────────────│
│ bidirectional byte stream │ │
│◄──────────────────────►│◄─────────────────────►│◄─────────────►│The consumer never connects directly to the VM. The entire SSH session is tunnelled through the libp2p overlay network, which handles NAT traversal, peer discovery, and encrypted transport.
SSH Gateway
The SSH gateway (node/ssh_gateway.go) is a standard SSH server that authenticates consumers using their lease's AccessPubKey and proxies the session to the provider node.
Configuration
The gateway is enabled with the -ssh-port flag:
xe node -provide -ssh-port 2222Flag
Default
Description
-ssh-port
0 (disabled)
TCP port for the SSH gateway listener
Host key
The gateway generates and persists an ed25519 host key at {dataDir}/ssh_host_key. This key is created on first run and reused across restarts, so the host fingerprint remains stable.
Authentication
The SSH username is the 64-character hex lease hash. This tells the gateway which lease the consumer wants to access.
Authentication flow:
- Client connects with
ssh -i <key> -p 2222 <leaseHash>@<host> - Gateway extracts the lease hash from the username field
- Gateway looks up the lease in the store:
- Lease must exist
- Lease must not be settled
- Lease must have a non-empty
AccessPubKey
- Gateway compares the client's presented ed25519 public key against the lease's
AccessPubKey(both as hex-encoded 32-byte keys) - If the key matches, authentication succeeds
[!NOTE] Key format The
AccessPubKeyis stored as a 64-character hex string representing the raw 32-byte ed25519 public key. This is the same key the consumer passed in the lease block'saccess_pub_keyfield. The SSH client must use the corresponding ed25519 private key.
Channel types
The gateway supports two SSH channel types:
session— standard shell channel (PTY + shell request). The gateway opens a tunnel and copies bytes between the SSH channel and the tunnel stream.direct-tcpip— TCP forwarding channel (used byssh -W/ ProxyJump). The gateway opens a tunnel and copies bytes without wrapping in a shell session. This allows the consumer's SSH client to negotiate end-to-end with the VM's sshd — no man in the middle.
The direct-tcpip channel is the recommended connection method. It provides end-to-end encryption between the consumer and the VM.
Session handling
After authentication, the gateway:
- Accepts the SSH channel (
sessionordirect-tcpip) - Looks up the provider's peer ID via the account directory (
PeerForAccount(lease.Provider)) - Opens a libp2p tunnel to the provider using the lease hash
- Copies bytes bidirectionally between the SSH channel and the tunnel stream
- Closes when either end disconnects
Usage
The xe ssh command handles the connection automatically:
xe ssh <leaseHash>This uses ProxyJump internally — the SSH client negotiates end-to-end with the VM's sshd through a direct-tcpip channel on the gateway:
# What xe ssh does internally:
ssh -o ProxyCommand="ssh -p 2222 -i ~/.xe/wallet.lease_key -W %h:%p <leaseHash>@ldn.test.network" \
-i ~/.xe/wallet.lease_key root@vmSSH key management
SSH keys are per-wallet, derived from the wallet file path:
Wallet path
SSH key path
~/.xe/wallet.seed
~/.xe/wallet.lease_key
/tmp/other.seed
/tmp/other.lease_key
Each wallet generates its own SSH key on first xe lease. The key is reused for all leases from that wallet. Different wallets cannot access each other's VMs — the gateway checks the key against the lease's access_pub_key.
Tunnel Protocol
The tunnel protocol (net/tunnel.go) is a libp2p stream protocol that proxies TCP connections from gateway nodes to VMs on provider nodes.
Protocol ID
/xe/tunnel/2.0.0Handshake
Step
Direction
Data
Description
1
Client → Provider
32 bytes (lease hash, hex-decoded)
Identifies which VM to connect to
2
Provider → Client
1 byte (0x00 = OK, 0x01 = error)
Status response
3
Provider → Client
Up to 4KB (if error)
Error message string
After a successful handshake (0x00 status), the stream becomes a bidirectional byte pipe between the gateway's SSH channel and the VM's SSH server.
Provider-side handler
SetupTunnelHandler(host, validator, registry) registers a stream handler for /xe/tunnel/2.0.0. The registry is a TunnelRegistry that tracks active tunnels per lease hash so they can be torn down on settlement:
- Set 30-second handshake deadline
- Read 32-byte lease hash from the stream
- Validate the lease via
ValidateTunnelLease(leaseHash, peerID):- Lease must exist in store
- Lease must not be settled
- This node must be the lease's provider
- Any peer can open a tunnel (allows gateway relay — the gateway already authenticated the consumer via SSH)
- Dial the VM's SSH port via
DialSSH(leaseHash):- Returns a
net.Connto127.0.0.1:{sshPort}on the provider host
- Returns a
- Send OK byte (0x00)
- Clear deadline (data phase has no timeout)
- Bidirectional copy between the libp2p stream and the TCP connection to the VM
- Close both sides when either end disconnects
Consumer-side client
OpenTunnel(ctx, host, peerstore, leaseHash, providerPeerID):
- Look up provider peer in the DHT if not already in the peerstore
- Open a new libp2p stream to the provider using the tunnel protocol
- Write the 32-byte lease hash (hex-decoded to binary)
- Read the status byte:
0x00→ success, return the stream as anio.ReadWriteCloser0x01→ read error message (up to 4KB), return error
- Clear deadline for the data phase
Validator interface
The tunnel handler uses a TunnelValidator interface (implemented by the node):
type TunnelValidator interface {
ValidateTunnelLease(leaseHash string, peerID peer.ID) error
DialSSH(leaseHash string) (net.Conn, error)
}ValidateTunnelLease checks that the lease exists, is not yet settled, and that this node is the provider. DialSSH opens a TCP connection to the VM's local SSH port via the LimaManager.
End-to-end flow
Complete flow for a consumer accessing a leased VM via SSH:
1. Consumer creates lease block with access_pub_key (ed25519 hex)
2. Provider auto-accepts lease, provisions Lima VM with SSH key
3. VM boots Alpine Linux, cloud-init installs openssh and injects key
4. Consumer connects: ssh -i key -p 2222 <leaseHash>@<gateway>
5. Gateway authenticates: matches key against lease's AccessPubKey
6. Gateway opens libp2p tunnel to provider (protocol /xe/tunnel/2.0.0)
7. Provider validates lease, dials VM's local SSH port (127.0.0.1:N)
8. Bidirectional proxy: SSH ↔ gateway ↔ libp2p ↔ provider ↔ VM
9. Consumer has interactive shell inside the Alpine VM
10. On lease settlement, VM is torn down, tunnel becomes invalidSecurity properties
Property
Mechanism
Authentication
Ed25519 key matching against on-chain AccessPubKey
Authorisation
Only the holder of the lease's private key can connect. Keys are per-wallet, preventing cross-wallet access.
End-to-end encryption
With ProxyJump/direct-tcpip, the consumer's SSH negotiates directly with the VM's sshd — the gateway only sees encrypted bytes
Transport encryption
libp2p noise protocol encrypts the tunnel between gateway and provider
Lease validity
Tunnel handler rejects connections to settled or non-existent leases
No direct exposure
VM's SSH port is only on localhost; unreachable without the tunnel
Handshake timeout
30-second deadline prevents hanging connections during handshake
Connection limit
maxSSHConnections semaphore (100) limits concurrent SSH sessions
HTTP tunnel endpoint
An alternative tunnel mechanism is available via HTTP upgrade at:
POST /tunnel/{leaseHash}/tcpThis endpoint (api/tunnel.go) hijacks the HTTP connection and proxies it to the provider's tunnel stream, allowing TCP-level access to the VM from HTTP clients. The lease must exist and not be settled. The caller must provide an X-Signature header containing a hex-encoded ed25519 signature of the lease hash, verified against the lease's AccessPubKey.
File reference
File
Description
node/ssh_gateway.go
SSH server implementation, authentication, session proxying
net/tunnel.go
libp2p tunnel protocol handler and client
api/tunnel.go
HTTP tunnel upgrade endpoint
vm/lima_manager.go
DialSSH() implementation (TCP dial to VM's local SSH port)