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

diagram
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
xe node -provide -ssh-port 2222
FlagDefaultDescription
-ssh-port0 (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:

  1. Client connects with ssh -i <key> -p 2222 <leaseHash>@<host>
  2. Gateway extracts the lease hash from the username field
  3. Gateway looks up the lease in the store:
    • Lease must exist
    • Lease must not be settled
    • Lease must have a non-empty AccessPubKey
  4. Gateway compares the client's presented ed25519 public key against the lease's AccessPubKey (both as hex-encoded 32-byte keys)
  5. If the key matches, authentication succeeds

#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 by ssh -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:

  1. Accepts the SSH channel (session or direct-tcpip)
  2. Looks up the provider's peer ID via the account directory (PeerForAccount(lease.Provider))
  3. Opens a libp2p tunnel to the provider using the lease hash
  4. Copies bytes bidirectionally between the SSH channel and the tunnel stream
  5. Closes when either end disconnects

#Usage

The xe ssh command handles the connection automatically:

xe
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:

shell
# 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@vm

#SSH key management

SSH keys are per-wallet, derived from the wallet file path:

Wallet pathSSH 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

text
/xe/tunnel/2.0.0

#Handshake

StepDirectionDataDescription
1Client → Provider32 bytes (lease hash, hex-decoded)Identifies which VM to connect to
2Provider → Client1 byte (0x00 = OK, 0x01 = error)Status response
3Provider → ClientUp 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:

  1. Set 30-second handshake deadline
  2. Read 32-byte lease hash from the stream
  3. 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)
  4. Dial the VM's SSH port via DialSSH(leaseHash):
    • Returns a net.Conn to 127.0.0.1:{sshPort} on the provider host
  5. Send OK byte (0x00)
  6. Clear deadline (data phase has no timeout)
  7. Bidirectional copy between the libp2p stream and the TCP connection to the VM
  8. Close both sides when either end disconnects

#Consumer-side client

OpenTunnel(ctx, host, peerstore, leaseHash, providerPeerID):

  1. Look up provider peer in the DHT if not already in the peerstore
  2. Open a new libp2p stream to the provider using the tunnel protocol
  3. Write the 32-byte lease hash (hex-decoded to binary)
  4. Read the status byte:
    • 0x00 → success, return the stream as an io.ReadWriteCloser
    • 0x01 → read error message (up to 4KB), return error
  5. Clear deadline for the data phase

#Validator interface

The tunnel handler uses a TunnelValidator interface (implemented by the node):

go
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:

text
1. Consumer creates lease block with access_pub_key (ed25519 hex)2. Provider auto-accepts lease, provisions Lima VM with SSH key3. VM boots Alpine Linux, cloud-init installs openssh and injects key4. Consumer connects: ssh -i key -p 2222 <leaseHash>@<gateway>5. Gateway authenticates: matches key against lease's AccessPubKey6. 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 ↔ VM9. Consumer has interactive shell inside the Alpine VM10. On lease settlement, VM is torn down, tunnel becomes invalid

#Security properties

PropertyMechanism
AuthenticationEd25519 key matching against on-chain AccessPubKey
AuthorisationOnly the holder of the lease's private key can connect. Keys are per-wallet, preventing cross-wallet access.
End-to-end encryptionWith ProxyJump/direct-tcpip, the consumer's SSH negotiates directly with the VM's sshd — the gateway only sees encrypted bytes
Transport encryptionlibp2p noise protocol encrypts the tunnel between gateway and provider
Lease validityTunnel handler rejects connections to settled or non-existent leases
No direct exposureVM's SSH port is only on localhost; unreachable without the tunnel
Handshake timeout30-second deadline prevents hanging connections during handshake
Connection limitmaxSSHConnections semaphore (100) limits concurrent SSH sessions

#HTTP tunnel endpoint

An alternative tunnel mechanism is available via HTTP upgrade at:

text
POST /tunnel/{leaseHash}/tcp

This 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

FileDescription
node/ssh_gateway.goSSH server implementation, authentication, session proxying
net/tunnel.golibp2p tunnel protocol handler and client
api/tunnel.goHTTP tunnel upgrade endpoint
vm/lima_manager.goDialSSH() implementation (TCP dial to VM's local SSH port)