Skip to content

SSH tunneling — RDP over SSH with local port forwarding #12

@CREVIOS

Description

@CREVIOS

Overview

Allow connecting to RDP servers through an SSH tunnel. This enables secure connections to machines behind firewalls/NATs, and adds an extra encryption layer.

Architecture

Drift Client                SSH Server              RDP Server
┌──────────────┐          ┌──────────────┐        ┌──────────┐
│              │          │              │        │          │
│  RDP Client  │──TCP───►│  SSH Tunnel   │──TCP──►│ Port 3389│
│  localhost   │  :14389  │  (forwarding) │        │          │
│  :14389      │          │              │        │          │
│              │          │              │        │          │
└──────────────┘          └──────────────┘        └──────────┘

1. Drift opens SSH connection to jump host
2. Creates local port forward: localhost:14389 → rdp-host:3389
3. IronRDP connects to localhost:14389
4. Traffic tunneled through SSH

Implementation Plan

Step 1: SSH connection via russh

russh is a pure-Rust async SSH library (tokio-based):

use russh::client;
use russh_keys::key;

let config = Arc::new(client::Config::default());
let mut session = client::connect(config, (ssh_host, ssh_port), handler).await?;
session.authenticate_publickey(username, key).await?;

// Local port forward
let channel = session.channel_open_direct_tcpip(
    rdp_host, rdp_port,      // remote destination
    "127.0.0.1", local_port  // local bind
).await?;

Step 2: TCP proxy

Create a local TCP listener on a random port. For each accepted connection, forward data bidirectionally through the SSH channel:

let listener = TcpListener::bind("127.0.0.1:0").await?;
let local_port = listener.local_addr()?.port();

// Accept connection and pipe to SSH channel
tokio::spawn(async move {
    let (tcp_stream, _) = listener.accept().await?;
    let (tcp_read, tcp_write) = tcp_stream.into_split();
    // Bidirectional copy between tcp_stream and ssh_channel
});

Step 3: Connection config

Add SSH tunnel settings to ConnectionConfig:

interface ConnectionConfig {
  // ... existing fields ...
  sshTunnel?: {
    enabled: boolean;
    host: string;        // SSH jump host
    port: number;        // SSH port (22)
    username: string;    // SSH username
    authMethod: 'key' | 'password';
    keyPath?: string;    // Path to private key
  };
}

Step 4: Auth methods

Support:

  • SSH key auth (default): ~/.ssh/id_ed25519, ~/.ssh/id_rsa
  • Password auth (fallback)
  • SSH agent forwarding
  • ~/.ssh/config ProxyJump parsing (reuse existing SSH config import)

Step 5: UI

  • "SSH Tunnel" section in Connection Form
  • Toggle: Enable SSH tunnel
  • Fields: SSH host, port, username, key path
  • Status indicator showing tunnel is active
  • Auto-detect SSH keys from ~/.ssh/

Files to create/modify

  • src-tauri/src/rdp/ssh_tunnel.rs — new module: SSH connection + TCP proxy
  • src-tauri/src/rdp/session.rs — establish tunnel before RDP connection
  • src-tauri/src/rdp/client.rs — connect to localhost tunnel port
  • src/types.ts — add SSH tunnel fields to ConnectionConfig
  • src/components/connections/ConnectionForm.tsx — SSH tunnel UI section
  • src-tauri/Cargo.toml — add russh, russh-keys

Dependencies

  • russh = "0.46" — async SSH client
  • russh-keys = "0.46" — SSH key handling

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsecuritySecurity related

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions