How DBX Implements SSH Tunnel Connections for Secure Database Access
DBX implements SSH tunnel connections as ordered transport layers that automatically chain through SSH, optional proxies, and HTTP tunnels, using the russh crate in crates/dbx-core/src/db/ssh_tunnel.rs to authenticate sessions and forward local TCP traffic to remote databases behind firewalls.
The open-source DBX project (t8y2/dbx) treats SSH tunnels as a transport-layer abstraction that sits transparently between your client application and the database. When a connection request includes transport_layers, DBX dynamically starts a local listener that forwards traffic through the configured SSH bastion host, eliminating the need for manual port forwarding or VPNs while keeping credentials encrypted in the local SQLite store.
Transport Layer Architecture Overview
DBX decouples connection security from database drivers by implementing a chainable transport layer system. Each layer processes traffic sequentially, with SSH typically forming the innermost secure channel.
Data Model and Configuration
The transport layer configuration resides in crates/dbx-core/src/models/connection.rs, where the SshTunnelConfig struct defines all SSH-specific parameters:
{
"type": "ssh",
"id": "bastion-1",
"host": "bastion.example.com",
"port": 22,
"user": "dbadmin",
"password": "••••",
"key_path": "~/.ssh/id_rsa",
"key_passphrase": "••••",
"connect_timeout_secs": 5,
"expose_lan": false,
"use_ssh_agent": true,
"ssh_agent_sock_path": "/run/user/1000/ssh-agent.sock"
}
When loading connections through packages/node-core/src/connections.ts, the normalizeTransportLayers helper converts legacy configuration fields into this modern transport_layers structure. Sensitive credentials like SSH passwords and key passphrases are extracted and stored in the encrypted connection_secrets table via crates/dbx-core/src/db/storage.rs, ensuring they never persist in plain text within the configuration object.
Starting the Tunnel Chain
When a query executes, DBX retrieves the effective transport layers by calling config.effective_transport_layers(). If this array contains entries, the async function start_transport_layers in crates/dbx-core/src/db/transport_layer_tunnel.rs orchestrates the tunnel creation:
let local_port = db::transport_layer_tunnel::start_transport_layers(
&connection.id,
&transport_layers,
remote_host,
remote_port,
&ssh_tunnel_manager,
&proxy_tunnel_manager,
&http_tunnel_manager,
).await?;
This function iterates over layers in order, creating a local TcpListener for each hop. Each subsequent layer connects through the previous layer's local port, resulting in a single local endpoint that transparently forwards to the ultimate database host.
SSH Tunnel Implementation Details
The core SSH logic lives in crates/dbx-core/src/db/ssh_tunnel.rs, which handles authentication, session maintenance, and TCP forwarding.
Authentication Methods
The connect_and_authenticate function attempts multiple authentication strategies in sequence:
- "none" probe – Discovers available authentication methods supported by the server
- Public-key authentication – Uses the private key specified in
key_pathif provided - Password authentication – Falls back to password credentials if configured
- SSH-agent authentication – Connects to the agent socket specified in
ssh_agent_sock_pathwhenuse_ssh_agentis enabled
The function returns a russh::client::Handle<SshClient> representing the authenticated session, which DBX maintains for the connection duration.
Connection Forwarding and Session Management
After authentication, DBX creates a local TcpListener on a dynamic port. For every incoming local connection, the implementation:
- Opens a direct TCP/IP channel using
session.channel_open_direct_tcpip - Forwards traffic bidirectionally between the local socket and the remote database host/port
- Runs an idle-session checker that pings the SSH server every 30 seconds
- Tears down the listener automatically if the SSH session disconnects
Robustness features include exponential back-off reconnection logic and maximum attempt limits, ensuring transient network failures do not leave dangling listeners or orphaned database connections.
Multi-Layer Transport Support
DBX supports complex network topologies by validating layer ordering in transport_layer_tunnel.rs. The plan_transport_layers function builds a routing table that records the target host/port for each layer and the next downstream destination.
Critical constraint: HTTP tunnels must occupy the outermost layer (closest to the client), while SSH tunnels typically sit innermost (closest to the database). This ordering ensures encrypted traffic flows correctly through proxy intermediaries.
Lifecycle Management and Cleanup
When queries complete or connections close, DBX invokes stop_transport_layers with the same layer count used during creation. This function iterates through the SSH, proxy, and HTTP tunnel managers, terminating each listener and releasing system resources:
dbx_core::db::transport_layer_tunnel::stop_transport_layers(
&conn.id,
conn.effective_transport_layers().len(),
&ssh_manager,
&proxy_manager,
&http_manager,
).await;
The cleanup process ensures no persistent port bindings remain open after the database connection terminates.
Configuration Examples
JSON Configuration for SSH Bastion
Define a MySQL connection routed through an SSH bastion server:
{
"name": "prod-mysql",
"db_type": "mysql",
"host": "127.0.0.1",
"port": 0,
"username": "app_user",
"password": "",
"transport_layers": [
{
"type": "ssh",
"id": "bastion",
"host": "bastion.example.com",
"port": 22,
"user": "deployer",
"key_path": "~/.ssh/id_ed25519",
"key_passphrase": "",
"connect_timeout_secs": 10,
"expose_lan": false,
"use_ssh_agent": true,
"ssh_agent_sock_path": ""
}
]
}
Note that port: 0 indicates DBX will overwrite this value with the dynamically allocated local tunnel port at runtime.
Rust Implementation Pattern
Programmatically open a tunneled connection:
let conn = dbx_core::connection::Connection::load("prod-mysql").await?;
let (host, port) = if conn.effective_transport_layers().is_empty() {
(conn.host.clone(), conn.port)
} else {
let local_port = dbx_core::db::transport_layer_tunnel::start_transport_layers(
&conn.id,
&conn.effective_transport_layers(),
&conn.host,
conn.port,
&ssh_manager,
&proxy_manager,
&http_manager,
).await?;
("127.0.0.1".to_string(), local_port)
};
// Connect driver through the local tunnel endpoint
let opts = mysql::Opts::from_url(&format!(
"mysql://{}:{}@{}:{}/{}",
conn.username, conn.password, host, port, conn.database.unwrap_or_default()
))?;
Summary
- Transport layer abstraction: DBX implements SSH tunnels as ordered transport layers in
transport_layer_tunnel.rs, supporting chained configurations with proxies and HTTP tunnels. - On-demand startup: Tunnels initialize automatically when
effective_transport_layers()returns non-empty results, binding to local ports that database drivers use as their connection endpoints. - Multi-method authentication: The
ssh_tunnel.rsimplementation tries public-key, password, and SSH-agent authentication sequentially using therusshcrate. - Session resilience: Automatic keep-alive checks every 30 seconds and exponential back-off reconnection prevent dropped connections from failing silently.
- Secure credential storage: SSH passwords and key passphrases are extracted from configuration and stored in the encrypted SQLite
connection_secretstable viastorage.rs.
Frequently Asked Questions
How does DBX handle authentication when both a key file and SSH agent are configured?
DBX attempts authentication methods in a specific order: first public-key authentication using the specified key_path, then password authentication if configured, and finally SSH-agent authentication if use_ssh_agent is true. This cascade ensures compatibility with different server configurations while prioritizing key-based methods.
Can DBX route traffic through multiple SSH hops or bastion hosts?
Yes, DBX supports ordered transport layers that can chain multiple SSH tunnels, proxies, or HTTP tunnels. The plan_transport_layers function in transport_layer_tunnel.rs validates the sequence and builds a routing table where each layer connects through the previous layer's local listener, allowing multi-hop configurations.
What happens if the SSH connection drops while a database query is running?
The ssh_tunnel.rs implementation includes an idle-session checker that pings the SSH server every 30 seconds. If the session disappears, DBX tears down the local listener and triggers reconnection logic with exponential back-off. The database driver will receive a connection error, allowing the application to retry or fail gracefully.
Where does DBX store SSH passwords and private key passphrases?
According to the DBX source code in packages/node-core/src/connections.ts and crates/dbx-core/src/db/storage.rs, sensitive credentials are extracted during configuration loading and stored in an encrypted SQLite table called connection_secrets. The in-memory configuration object retains only non-sensitive fields, ensuring credentials are never exposed in logs or memory dumps.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →