CrackedRuby CrackedRuby

Overview

SSH (Secure Shell) provides encrypted communication channels between networked devices. The protocol operates at the application layer and establishes secure connections over TCP port 22 by default. SSH replaced insecure protocols like Telnet and rlogin by encrypting all transmitted data including authentication credentials.

The protocol architecture consists of three main components: the transport layer handles server authentication and encryption setup, the authentication layer verifies client identity, and the connection layer multiplexes multiple logical channels over a single SSH connection. Each layer operates independently with defined responsibilities.

SSH supports multiple authentication methods including password-based, public key cryptography, and keyboard-interactive authentication. Public key authentication remains the most secure method as it eliminates password transmission over the network. The protocol negotiates encryption algorithms during connection establishment, selecting the strongest mutually supported cipher.

require 'net/ssh'

# Basic SSH connection
Net::SSH.start('example.com', 'username', password: 'secret') do |ssh|
  result = ssh.exec!('hostname')
  puts result
end

SSH sessions persist after initial authentication, allowing multiple operations without repeated credential exchange. The protocol maintains connection state and handles channel management transparently. Session multiplexing reduces overhead by reusing established connections for subsequent operations.

Key Principles

The SSH protocol operates through a layered architecture where each layer provides specific security and communication functions. The transport layer establishes the encrypted tunnel, the user authentication layer verifies identity, and the connection layer provides multiplexed channels for various services.

Transport Layer Protocol

The transport layer initiates with version exchange where both client and server advertise supported SSH protocol versions. Following version negotiation, algorithm negotiation occurs where both parties agree on key exchange methods, encryption algorithms, MAC algorithms, and compression methods. The protocol selects algorithms based on client preference order matched against server capabilities.

Key exchange establishes shared secrets without transmitting them across the network. Diffie-Hellman key exchange variants generate session keys that encrypt subsequent communication. The server provides its host key during this phase, which clients verify against known hosts to prevent man-in-the-middle attacks. After successful key exchange, the protocol derives encryption keys, integrity keys, and initialization vectors from the shared secret.

Authentication Layer

Authentication begins after transport layer encryption activates. The protocol supports multiple authentication methods that can be attempted sequentially until one succeeds. Public key authentication requires the client to prove possession of the private key corresponding to an authorized public key. The server sends a challenge that only the private key holder can correctly sign.

Password authentication transmits credentials encrypted by the transport layer. While simpler than key-based authentication, password methods remain vulnerable to brute force attacks if passwords lack sufficient entropy. Keyboard-interactive authentication provides flexible authentication flows supporting one-time passwords and multi-factor authentication schemes.

Connection Layer

The connection layer multiplexes multiple channels over the authenticated connection. Channel types include session channels for shell access and command execution, direct-tcpip channels for port forwarding, and forwarded-tcpip channels for reverse port forwarding. Each channel operates independently with separate data streams and flow control.

Session channels support interactive shells, command execution, and subsystem requests. The protocol specifies subsystems like SFTP (SSH File Transfer Protocol) and SCP (Secure Copy Protocol) that operate over session channels. Channels maintain separate stdin, stdout, and stderr streams, preserving the semantics of local command execution.

Cryptographic Negotiation

During connection establishment, both parties exchange lists of supported algorithms in preference order. The protocol selects the first client algorithm that appears in the server's list for each algorithm category. Categories include key exchange algorithms, server host key algorithms, encryption algorithms for each direction, MAC algorithms for each direction, and compression algorithms for each direction.

Modern implementations prefer elliptic curve cryptography for key exchange (curve25519, ECDH) and authenticated encryption modes (AES-GCM, ChaCha20-Poly1305). The protocol renegotiates keys periodically or after transmitting specified data volumes to limit exposure if keys become compromised.

Security Implications

SSH protocol security depends on proper implementation of cryptographic primitives, secure key management, and defense against various attack vectors. The protocol's security model assumes network connections are untrusted and potentially compromised by active attackers.

Host Key Verification

Host key verification prevents man-in-the-middle attacks by confirming server identity before authentication. Clients maintain a known_hosts file mapping hostnames to verified public keys. First connection to a new host requires manual verification of the host key fingerprint through an out-of-band channel.

require 'net/ssh'

# Strict host key verification
Net::SSH.start('example.com', 'username',
  verify_host_key: :always,
  password: 'secret'
) do |ssh|
  ssh.exec!('whoami')
end

# Accept unknown hosts (development only)
Net::SSH.start('new-host.com', 'username',
  verify_host_key: :never,
  password: 'secret'
) do |ssh|
  ssh.exec!('pwd')
end

Changing host keys indicate potential security issues including server reinstallation, key rotation, or active attacks. The protocol provides no mechanism for automatic host key validation, requiring manual intervention when keys change. Production systems should implement automated host key distribution through configuration management rather than disabling verification.

Key-Based Authentication Security

Private keys require protection equivalent to passwords since possession of an unencrypted private key grants access to associated accounts. Keys should use passphrase encryption to protect against theft. Modern key formats like OpenSSH's native format provide stronger encryption than legacy PEM formats.

require 'net/ssh'

# Key-based authentication with encrypted key
Net::SSH.start('example.com', 'username',
  keys: ['~/.ssh/id_ed25519'],
  passphrase: 'key_passphrase',
  auth_methods: ['publickey']
) do |ssh|
  result = ssh.exec!('ls -la')
  puts result
end

Key rotation practices mitigate risk from key compromise. Organizations should establish key lifecycle policies defining maximum key age and rotation procedures. Compromised keys must be immediately removed from authorized_keys files on all systems.

Algorithm Selection

Cryptographic algorithm selection directly impacts connection security. Weak algorithms remain supported for backward compatibility but should be disabled in security-sensitive environments. The protocol allows either party to terminate connections if acceptable algorithms cannot be negotiated.

Disable deprecated algorithms including 3DES, RC4, MD5-based MACs, and CBC mode ciphers vulnerable to plaintext recovery attacks. Prefer authenticated encryption modes eliminating the need for separate MAC algorithms. Configuration should specify minimum key lengths and restrict key exchange to methods providing forward secrecy.

Forward Secrecy

Forward secrecy ensures session decryption remains impossible even if long-term keys become compromised. Diffie-Hellman key exchange variants provide forward secrecy by generating ephemeral session keys. Static RSA key exchange lacks forward secrecy as compromised host keys enable decryption of recorded sessions.

Access Control

SSH access control operates at multiple layers. Network filtering restricts SSH connections to authorized sources. Host-based authentication validates client hostnames against permitted systems. User-level controls define which public keys can authenticate to specific accounts and what commands they can execute.

Command restrictions in authorized_keys files limit key usage to specific operations. The command= option forces execution of specified commands regardless of client requests. This pattern implements automated systems requiring SSH access without granting interactive shell access.

# Using Net::SSH with restricted commands
require 'net/ssh'

Net::SSH.start('backup-server.com', 'backup-user',
  keys: ['~/.ssh/backup_key'],
  auth_methods: ['publickey']
) do |ssh|
  # This key might be restricted to rsync commands
  output = ssh.exec!('rsync --version')
  puts output
end

Ruby Implementation

Ruby's Net::SSH library provides SSH protocol implementation supporting all major authentication methods, port forwarding, and file transfer operations. The library handles protocol negotiation, encryption, and channel management transparently.

Basic Connection Management

Net::SSH uses block syntax for automatic connection cleanup. Connections close automatically when blocks exit, preventing resource leaks. The library maintains connection state and reuses existing connections when multiple operations execute within a single block.

require 'net/ssh'

# Connection with automatic cleanup
Net::SSH.start('server.example.com', 'deploy',
  keys: ['~/.ssh/id_rsa'],
  auth_methods: ['publickey']
) do |ssh|
  # Execute multiple commands
  ssh.exec!('cd /var/www && git pull')
  ssh.exec!('systemctl restart webapp')
end
# Connection automatically closed

Connections without blocks require explicit closing. The start method returns a session object that must be closed to release resources. Manual connection management suits scenarios requiring connection persistence across method boundaries.

require 'net/ssh'

# Manual connection management
session = Net::SSH.start('database.example.com', 'dba',
  password: ENV['DB_SSH_PASSWORD'],
  port: 2222
)

begin
  session.exec!('pg_dump production > backup.sql')
  session.exec!('gzip backup.sql')
ensure
  session.close
end

Authentication Methods

The library supports multiple authentication methods attempted in specified order. Password authentication provides simplicity but less security than key-based methods. Key authentication requires specifying key file paths or using ssh-agent.

require 'net/ssh'

# Multiple authentication methods
Net::SSH.start('server.example.com', 'admin',
  keys: ['~/.ssh/id_ed25519', '~/.ssh/id_rsa'],
  password: 'fallback_password',
  auth_methods: ['publickey', 'password']
) do |ssh|
  ssh.exec!('uptime')
end

# SSH agent authentication
Net::SSH.start('server.example.com', 'user',
  auth_methods: ['publickey'],
  use_agent: true
) do |ssh|
  ssh.exec!('hostname')
end

Command Execution Patterns

The exec! method executes commands synchronously and returns output as strings. This method suits simple commands where output fits in memory. Complex commands requiring real-time output processing need asynchronous execution.

require 'net/ssh'

Net::SSH.start('app-server.com', 'deploy') do |ssh|
  # Simple synchronous execution
  output = ssh.exec!('df -h')
  puts output
  
  # Asynchronous execution with callbacks
  ssh.open_channel do |channel|
    channel.exec('tail -f /var/log/app.log') do |ch, success|
      abort "Command failed" unless success
      
      channel.on_data do |ch, data|
        puts "STDOUT: #{data}"
      end
      
      channel.on_extended_data do |ch, type, data|
        puts "STDERR: #{data}" if type == 1
      end
    end
  end
  
  ssh.loop
end

Port Forwarding

Local port forwarding redirects local ports through SSH to remote destinations. This technique accesses services behind firewalls or provides encrypted tunnels for unencrypted protocols. Remote port forwarding exposes local services to remote networks.

require 'net/ssh'

Net::SSH.start('gateway.example.com', 'user') do |ssh|
  # Local port forwarding: localhost:3307 -> remote:3306
  ssh.forward.local(3307, 'database.internal', 3306)
  
  # Access forwarded port from another connection
  require 'mysql2'
  client = Mysql2::Client.new(
    host: '127.0.0.1',
    port: 3307,
    username: 'dbuser',
    password: 'dbpass'
  )
  
  results = client.query('SELECT COUNT(*) FROM users')
  puts results.first
  
  ssh.loop { true }
end

File Transfer Operations

Net::SCP provides SCP protocol implementation for file transfers. The library handles file permissions and timestamps automatically. Recursive directory transfers require explicit options.

require 'net/ssh'
require 'net/scp'

Net::SSH.start('file-server.com', 'user') do |ssh|
  # Upload single file
  ssh.scp.upload!('/local/path/file.txt', '/remote/path/file.txt')
  
  # Download file
  ssh.scp.download!('/remote/logs/app.log', '/local/logs/app.log')
  
  # Recursive directory upload
  ssh.scp.upload!('/local/directory', '/remote/directory',
    recursive: true
  )
  
  # Upload with progress callback
  ssh.scp.upload!('/local/large-file.zip', '/remote/large-file.zip') do |ch, name, sent, total|
    progress = (sent.to_f / total * 100).round(2)
    print "\rUploading: #{progress}%"
  end
  puts "\nUpload complete"
end

Implementation Approaches

SSH implementation strategies vary based on automation requirements, scale, and security constraints. Approaches differ in connection management, error handling, and integration with existing infrastructure.

Direct Connection Pattern

Direct connections work for simple automation tasks requiring limited SSH operations. Each operation establishes a new connection, authenticates, executes commands, and closes. This pattern suits scripts running infrequently where connection overhead remains acceptable.

require 'net/ssh'

def deploy_application(host, user)
  Net::SSH.start(host, user, keys: ['~/.ssh/deploy_key']) do |ssh|
    commands = [
      'cd /var/www/app',
      'git pull origin main',
      'bundle install --deployment',
      'systemctl restart app'
    ]
    
    commands.each do |cmd|
      output = ssh.exec!(cmd)
      puts "#{cmd}: #{output}"
    end
  end
rescue Net::SSH::AuthenticationFailed
  abort "Authentication failed for #{user}@#{host}"
rescue Net::SSH::ConnectionTimeout
  abort "Connection timeout for #{host}"
end

deploy_application('web1.example.com', 'deploy')

Connection Pool Pattern

Connection pooling maintains persistent connections for reuse across multiple operations. This approach reduces latency by eliminating repeated authentication overhead. Pools manage connection lifecycle including validation, reuse, and cleanup.

require 'net/ssh'
require 'connection_pool'

class SSHConnectionPool
  def initialize(host, user, size: 5)
    @pool = ConnectionPool.new(size: size, timeout: 5) do
      Net::SSH.start(host, user, 
        keys: ['~/.ssh/id_rsa'],
        keepalive: true,
        keepalive_interval: 60
      )
    end
  end
  
  def exec(command)
    @pool.with do |ssh|
      ssh.exec!(command)
    end
  end
  
  def shutdown
    @pool.shutdown { |ssh| ssh.close }
  end
end

# Usage
pool = SSHConnectionPool.new('app-server.com', 'deploy', size: 10)

50.times do
  Thread.new do
    result = pool.exec('uptime')
    puts result
  end
end

pool.shutdown

Batch Operations Pattern

Batch operations execute commands across multiple hosts concurrently. This pattern parallelizes deployment tasks, reducing total execution time. Thread pools or process pools manage concurrency while limiting resource consumption.

require 'net/ssh'
require 'concurrent-ruby'

class BatchSSHExecutor
  def initialize(hosts, user)
    @hosts = hosts
    @user = user
  end
  
  def execute(command, max_threads: 10)
    pool = Concurrent::FixedThreadPool.new(max_threads)
    futures = @hosts.map do |host|
      Concurrent::Future.execute(executor: pool) do
        execute_on_host(host, command)
      end
    end
    
    results = futures.map(&:value)
    pool.shutdown
    pool.wait_for_termination
    results
  end
  
  private
  
  def execute_on_host(host, command)
    Net::SSH.start(host, @user, keys: ['~/.ssh/id_rsa']) do |ssh|
      output = ssh.exec!(command)
      { host: host, output: output, success: true }
    end
  rescue => e
    { host: host, error: e.message, success: false }
  end
end

# Execute across multiple servers
hosts = ['web1.example.com', 'web2.example.com', 'web3.example.com']
executor = BatchSSHExecutor.new(hosts, 'deploy')
results = executor.execute('systemctl status nginx')

results.each do |result|
  if result[:success]
    puts "#{result[:host]}: #{result[:output]}"
  else
    puts "#{result[:host]} ERROR: #{result[:error]}"
  end
end

Gateway/Jump Host Pattern

Gateway patterns route connections through intermediate hosts when target systems lack direct network access. Jump hosts authenticate initial connections then establish secondary connections to final destinations. This pattern implements common security architectures separating public and private networks.

require 'net/ssh'
require 'net/ssh/gateway'

# Connect through bastion host
gateway = Net::SSH::Gateway.new('bastion.example.com', 'user',
  keys: ['~/.ssh/bastion_key']
)

# Access internal server through gateway
gateway.ssh('internal-db.private', 'dbadmin',
  keys: ['~/.ssh/internal_key']
) do |ssh|
  result = ssh.exec!('pg_dump production')
  File.write('backup.sql', result)
end

gateway.shutdown!

Tools & Ecosystem

The SSH ecosystem includes various tools and libraries for different use cases ranging from simple command execution to complex orchestration frameworks.

Net::SSH

Net::SSH provides the core SSH protocol implementation in Ruby. The library handles transport layer security, authentication negotiation, and channel multiplexing. Most Ruby SSH functionality builds on Net::SSH as a foundation.

require 'net/ssh'

# Connection options
options = {
  keys: ['~/.ssh/id_ed25519'],
  port: 2222,
  timeout: 30,
  forward_agent: true,
  compression: true,
  encryption: ['aes256-gcm@openssh.com', 'chacha20-poly1305@openssh.com'],
  verify_host_key: :always
}

Net::SSH.start('server.example.com', 'user', options) do |ssh|
  ssh.exec!('env')
end

Net::SCP

Net::SCP extends Net::SSH with SCP protocol support for file transfers. The library maintains compatibility with OpenSSH's SCP implementation while providing Ruby-friendly interfaces.

Capistrano

Capistrano builds on Net::SSH to provide deployment automation. The framework defines deployment workflows as reusable tasks executed across multiple servers. Capistrano handles connection management, error recovery, and rollback capabilities.

# Capfile
require 'capistrano/setup'
require 'capistrano/deploy'

# config/deploy.rb
set :application, 'myapp'
set :repo_url, 'git@github.com:user/myapp.git'

server 'web1.example.com', user: 'deploy', roles: %w{app web}
server 'web2.example.com', user: 'deploy', roles: %w{app web}

namespace :deploy do
  task :restart do
    on roles(:app) do
      execute :systemctl, 'restart', 'myapp'
    end
  end
end

SSHKit

SSHKit provides lower-level SSH execution for building deployment tools. The library offers command execution abstractions, connection pooling, and output formatting. Capistrano uses SSHKit internally.

require 'sshkit'
require 'sshkit/dsl'

SSHKit::Backend::Netssh.configure do |ssh|
  ssh.connection_timeout = 30
  ssh.ssh_options = {
    keys: ['~/.ssh/deploy_key'],
    forward_agent: false,
    auth_methods: ['publickey']
  }
end

on ['web1.example.com', 'web2.example.com'], in: :parallel do
  within '/var/www/app' do
    as 'deploy' do
      execute :git, :pull
      execute :bundle, :install
    end
  end
end

Vagrant

Vagrant uses SSH for managing development virtual machines. The tool automates SSH key generation, configuration, and connection management. Vagrant SSH provides access to development environments without manual configuration.

Common Pitfalls

SSH implementation commonly encounters issues related to authentication, connection management, and security configuration. Understanding typical problems prevents repeated debugging cycles.

Host Key Verification Failures

Changed host keys cause connection failures when strict verification activates. This occurs during server reinstallation, key rotation, or when DNS returns different hosts for the same hostname. Blindly disabling verification eliminates man-in-the-middle attack protection.

require 'net/ssh'

# Wrong: Disabling verification globally
Net::SSH.start('server.com', 'user',
  verify_host_key: :never  # Dangerous!
) do |ssh|
  ssh.exec!('hostname')
end

# Correct: Update known_hosts or verify new key
begin
  Net::SSH.start('server.com', 'user',
    verify_host_key: :always
  ) do |ssh|
    ssh.exec!('hostname')
  end
rescue Net::SSH::HostKeyMismatch => e
  puts "Host key changed!"
  puts "New fingerprint: #{e.fingerprint}"
  # Verify through out-of-band channel before proceeding
end

Authentication Method Ordering

Incorrect authentication method ordering causes failures when servers limit authentication attempts. Servers disconnect clients exceeding attempt limits before trying all configured methods. Specify authentication methods explicitly in order of likelihood.

require 'net/ssh'

# Wrong: Password tried before keys
Net::SSH.start('server.com', 'user',
  password: 'secret',
  keys: ['~/.ssh/id_rsa']
) do |ssh|
  ssh.exec!('whoami')
end

# Correct: Explicit method ordering
Net::SSH.start('server.com', 'user',
  keys: ['~/.ssh/id_rsa'],
  password: 'secret',
  auth_methods: ['publickey', 'password']
) do |ssh|
  ssh.exec!('whoami')
end

Connection Leaks

Failing to close connections exhausts file descriptors and connection limits. This occurs when exception handling prevents reaching close statements or when blocks exit prematurely. Connection pools mitigate leaks but proper cleanup remains essential.

require 'net/ssh'

# Wrong: Connection not closed on exception
def run_command(host, command)
  ssh = Net::SSH.start(host, 'user')
  ssh.exec!(command)
  ssh.close
end

# Correct: Ensure cleanup
def run_command(host, command)
  ssh = Net::SSH.start(host, 'user')
  begin
    ssh.exec!(command)
  ensure
    ssh.close
  end
end

# Better: Use block form
def run_command(host, command)
  Net::SSH.start(host, 'user') do |ssh|
    ssh.exec!(command)
  end
end

Timeout Configuration

Default timeouts may be inappropriate for long-running operations or slow networks. Commands exceeding timeouts terminate without completing. Set timeouts based on expected operation duration plus buffer for network variability.

require 'net/ssh'

# Wrong: Default timeout for long operations
Net::SSH.start('server.com', 'user') do |ssh|
  ssh.exec!('pg_dump large_database')  # May timeout
end

# Correct: Appropriate timeout
Net::SSH.start('server.com', 'user', timeout: 300) do |ssh|
  channel = ssh.open_channel do |ch|
    ch.exec('pg_dump large_database') do |ch, success|
      ch.on_data do |ch, data|
        File.write('backup.sql', data, mode: 'a')
      end
    end
  end
  channel.wait
end

PTY Allocation

Commands requiring pseudo-terminal allocation fail without explicit PTY requests. Interactive programs and some system commands expect terminal environments. PTY allocation affects how commands process signals and handle input/output.

require 'net/ssh'

Net::SSH.start('server.com', 'user') do |ssh|
  # Wrong: Interactive command without PTY
  ssh.exec!('sudo systemctl restart nginx')  # May hang
  
  # Correct: Request PTY for interactive commands
  channel = ssh.open_channel do |ch|
    ch.request_pty do |ch, success|
      abort "PTY request failed" unless success
      
      ch.exec('sudo systemctl restart nginx') do |ch, success|
        abort "Command failed" unless success
      end
    end
  end
  channel.wait
end

Command Injection

Unsanitized user input in SSH commands enables arbitrary code execution on remote systems. Shell metacharacters allow command chaining and parameter injection. Validate and escape all user-provided data before inclusion in commands.

require 'net/ssh'
require 'shellwords'

# Wrong: Direct interpolation
def backup_file(ssh, filename)
  ssh.exec!("tar czf backup.tar.gz #{filename}")
end

# Correct: Escape shell metacharacters
def backup_file(ssh, filename)
  escaped = Shellwords.escape(filename)
  ssh.exec!("tar czf backup.tar.gz #{escaped}")
end

# Better: Use array form when available
def backup_file(ssh, filename)
  # Note: Net::SSH exec! doesn't support array form
  # Escape remains necessary
  escaped = Shellwords.escape(filename)
  ssh.exec!("tar czf backup.tar.gz #{escaped}")
end

Reference

Connection Options

Option Type Description
port Integer TCP port number, default 22
timeout Integer Connection timeout in seconds
keepalive Boolean Enable TCP keepalive
keepalive_interval Integer Keepalive probe interval in seconds
compression Boolean Enable compression
forward_agent Boolean Enable SSH agent forwarding
verify_host_key Symbol Host key verification mode: :always, :never, :accept_new_or_local_tunnel
auth_methods Array Authentication methods to try in order
keys Array Private key file paths
keys_only Boolean Disable password authentication
password String Password for password authentication
passphrase String Private key passphrase
use_agent Boolean Use SSH agent for authentication
user_known_hosts_file String Path to known_hosts file

Authentication Methods

Method Description Security Level
publickey Public key cryptography High
password Password authentication Medium
keyboard-interactive Interactive challenge-response Medium
hostbased Host-based authentication High
gssapi-with-mic GSSAPI authentication High

Channel Types

Type Purpose Use Case
session Shell access and command execution Interactive sessions, remote commands
direct-tcpip Local port forwarding Database tunnels, service access
forwarded-tcpip Remote port forwarding Exposing local services remotely
x11 X11 forwarding GUI application display

Common Algorithms

Category Recommended Notes
Key Exchange curve25519-sha256, ecdh-sha2-nistp256 Provides forward secrecy
Server Host Key ssh-ed25519, ecdsa-sha2-nistp256 ED25519 preferred
Encryption chacha20-poly1305@openssh.com, aes256-gcm@openssh.com Authenticated encryption
MAC hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com Encrypt-then-MAC
Compression none, zlib@openssh.com Compression after authentication

Error Classes

Class Cause Recovery Strategy
Net::SSH::AuthenticationFailed Invalid credentials Verify credentials, check permissions
Net::SSH::ConnectionTimeout Network unreachable Check network, verify host up
Net::SSH::HostKeyMismatch Changed host key Verify key change legitimate, update known_hosts
Net::SSH::Disconnect Server closed connection Check server logs, retry connection
Net::SSH::Exception General SSH error Review error message, check configuration

File Transfer Methods

Method Protocol Best For
Net::SCP.upload! SCP Single files, simple transfers
Net::SCP.download! SCP Retrieving files
Net::SFTP SFTP Directory operations, file listing
rsync over SSH rsync Synchronization, large datasets

Port Forwarding Patterns

Pattern Command Description
Local Forward ssh.forward.local(local_port, remote_host, remote_port) Access remote service locally
Remote Forward ssh.forward.remote(remote_port, local_host, local_port) Expose local service remotely
Dynamic Forward ssh.forward.local_socket(local_port) SOCKS proxy through SSH