or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

bigquery-client.mdclickhouse-client.mdcore-types.mdcredentials.mddatabricks-client.mdfactory-functions.mdindex.mdpostgres-client.mdredshift-client.mdsnowflake-client.mdsql-builders.mdssh-tunnel.mdtrino-client.md
tile.json

ssh-tunnel.mddocs/

SSH Tunnel Support

Secure SSH tunnels for PostgreSQL and Redshift connections through bastion hosts.

Quick Start

import { PostgresWarehouseClient } from '@lightdash/warehouses';

const client = new PostgresWarehouseClient({
  type: 'postgres',
  host: 'internal-db.local',
  port: 5432,
  user: 'dbuser',
  password: 'dbpass',
  dbname: 'warehouse',
  schema: 'public',
  sslmode: 'disable',
  // SSH tunnel config
  useSshTunnel: true,
  sshTunnelHost: 'bastion.example.com',
  sshTunnelPort: 22,
  sshTunnelUser: 'sshuser',
  sshTunnelPrivateKey: '-----BEGIN RSA PRIVATE KEY-----\n...',
});

await client.test(); // Tunnel created automatically

Supported Warehouses

WarehouseSSH Tunnel Support
PostgreSQL
Redshift
BigQuery✗ (uses API)
ClickHouse
Databricks✗ (uses API)
Snowflake✗ (uses API)
Trino✗ (uses HTTP)

Manual Tunnel Management

import { SshTunnel } from '@lightdash/warehouses';

const tunnel = new SshTunnel(credentials);
const modifiedCreds = await tunnel.connect();
console.log('Tunnel port:', tunnel.localPort);

const client = new PostgresWarehouseClient(modifiedCreds);
await client.test();

await tunnel.disconnect();

Tunnel Workflow

Original:  Client -> internal-db.local:5432 (blocked)

With SSH:  Client -> 127.0.0.1:random_port
           -> [SSH Tunnel] -> bastion.example.com:22
           -> internal-db.local:5432 (accessible)

SSH Configuration Fields

{
  useSshTunnel: true,
  sshTunnelHost: 'bastion.example.com',
  sshTunnelPort: 22, // Default: 22
  sshTunnelUser: 'sshuser',
  sshTunnelPrivateKey: '-----BEGIN RSA PRIVATE KEY-----\n...',
  sshTunnelPublicKey: '...', // Optional, for reference
}

Private Key Format

Must be PEM format with newlines:

const privateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA1234567890...
[multiple lines]
-----END RSA PRIVATE KEY-----`;

Supported Key Types:

  • RSA: -----BEGIN RSA PRIVATE KEY-----
  • DSA: -----BEGIN DSA PRIVATE KEY-----
  • ECDSA: -----BEGIN EC PRIVATE KEY-----
  • Ed25519: -----BEGIN OPENSSH PRIVATE KEY-----

SshTunnel Class

class SshTunnel<T extends CreateWarehouseCredentials> {
  readonly originalCredentials: T;
  overrideCredentials: T;
  localPort: number | undefined;

  constructor(credentials: T);
  connect(): Promise<T>;
  disconnect(): Promise<void>;
}

Error Handling

try {
  const tunnel = new SshTunnel(credentials);
  const modifiedCreds = await tunnel.connect();
  const client = new PostgresWarehouseClient(modifiedCreds);
  await client.test();
} catch (error) {
  console.error('SSH tunnel failed:', error.message);
} finally {
  await tunnel.disconnect();
}

Common Issues

Connection Timeout

Error: SSH tunnel connection timeout

Solutions: Check bastion hostname, port, firewall, network connectivity

Authentication Failed

Error: SSH authentication failed

Solutions: Verify username, private key format, key matches public key on bastion

Port Forward Failed

Error: Failed to establish port forward

Solutions: Verify database host:port reachable from bastion, check firewall rules

Security Best Practices

Private Keys

  • Never commit to version control
  • Store in secrets manager or environment variables
  • Use encrypted keys when possible
  • Rotate regularly
  • Use dedicated keys (not personal SSH keys)

SSH Configuration

  • Use strong key types (Ed25519 or RSA 4096-bit)
  • Restrict bastion access with IP whitelisting
  • Use key passphrases (not currently supported)
  • Configure SSH timeouts
  • Monitor SSH access logs
  • Use dedicated service accounts

Network Security

  • Harden bastion host
  • Use key-based auth only (disable password auth)
  • Configure firewall rules on bastion
  • Use VPN + SSH for additional layers
  • Monitor tunnel connections

Multiple Tunnels

const pgTunnel = new SshTunnel(pgCredentials);
const rsTunnel = new SshTunnel(rsCredentials);

const pgCreds = await pgTunnel.connect();
const rsCreds = await rsTunnel.connect();

const pgClient = new PostgresWarehouseClient(pgCreds);
const rsClient = new RedshiftWarehouseClient(rsCreds);

// Use both clients...

await pgTunnel.disconnect();
await rsTunnel.disconnect();

Notes

  • Tunnel automatically selects random local port
  • PostgreSQL/Redshift clients manage tunnels automatically
  • Tunnel persists for client lifetime
  • One tunnel per client
  • No passphrase support for encrypted keys