sshtunnel Python Library

repository·master·Indexed 23 days ago

https://github.com/pahaz/sshtunnel

A Python library for creating SSH tunnels to access remote services behind firewalls or on private networks. It provides the SSHTunnelForwarder class for manual management, an open_tunnel context manager for automatic lifecycle handling, and a command-line interface. The library supports multiple authentication methods, SSH jumping through nested tunnels, and depends on paramiko.

Tokens
3.9K
Snippets
13
Records
15
Agent score
80%

What's inside sshtunnel

  1. Install sshtunnel

    master

    You can install sshtunnel using pip, easy_install, or conda. It requires paramiko as a dependency.

    Using pip:

    pip install sshtunnel

    Using easy_install:

    easy_install sshtunnel

    Using conda:

    conda install -c conda-forge sshtunnel

    From source: Clone the repository and run:

    python setup.py install
    pip install sshtunnel
  2. Best practices for using sshtunnel

    master

    When using sshtunnel, follow these recommendations:

    • Use the wrapper: Prefer using the open_tunnel() wrapper function.
    • Use Context Managers: Use the with statement to manage tunnels. This ensures that both the tunnel and the underlying SSH transports are automatically opened and closed correctly.
    • Avoid Deprecated Arguments: Do not use deprecated parameters, as they may be removed in future releases.
  3. Enable verbose logging and set timeouts for sshtunnel

    master

    To debug sshtunnel issues, you should explicitly set the SSH_TIMEOUT and TUNNEL_TIMEOUT to a known value and set the debug_level to 'TRACE'. It is also recommended to explicitly define the local_bind_address during debugging to avoid ambiguity.

    import sshtunnel
    
    sshtunnel.SSH_TIMEOUT = sshtunnel.TUNNEL_TIMEOUT = 5.0
    
    server = sshtunnel.open_tunnel(
        IP_ADDRESS_OR_HOSTNAME,
        ssh_username=USERNAME,
        ssh_password=PASSWORD,
        remote_bind_address=(REMOTE_BIND_IP, REMOTE_BIND_PORT),
        local_bind_address=('127.0.0.1', LOCAL_BIND_PORT),
        debug_level='TRACE',
    )
    
    server.start()
    print(server.local_bind_port)  # show assigned local port
    server.stop()
  4. Verify permissions to listen on a local port

    master

    If you encounter errors related to binding a local port, check if your current user/environment has permission to listen on the target LOCAL_BIND_PORT using a standard socket test.

    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('0.0.0.0', LOCAL_BIND_PORT))
    s.listen(1)
    s.close()
  5. Debug SSH connectivity with paramiko

    master

    Before troubleshooting sshtunnel, verify that you can connect to your SSH gateway or bastion host using paramiko. This helps determine if the issue lies with the network/SSH credentials or the tunnel library itself. Use the following pattern to test connectivity with a username and password, disabling agent and key lookups to ensure a clean test.

    import paramiko
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(IP_ADDRESS_OR_HOSTNAME,
                   username=USERNAME,
                   password=PASSWORD,
                   allow_agent=False,
                   look_for_keys=False,
                   timeout=5.0)
  6. Jump through multiple SSH tunnels

    master

    You can nest open_tunnel context managers to create a chain of tunnels (SSH jumping). This allows you to reach a target server that is only accessible through an intermediate gateway.

    import sshtunnel
    from paramiko import SSHClient
    
    # Tunnel 1: Connect to Gateway 1
    with sshtunnel.open_tunnel(
        ssh_address_or_host=('GW1_ip', 20022),
        remote_bind_address=('GW2_ip', 22),
    ) as tunnel1:
        print('Connection to tunnel1 (GW1_ip:GW1_port) OK...')
        
        # Tunnel 2: Connect to Target via Gateway 2 (using Tunnel 1 as the transport)
        with sshtunnel.open_tunnel(
            ssh_address_or_host=('localhost', tunnel1.local_bind_port),
            remote_bind_address=('target_ip', 22),
            ssh_username='GW2_user',
            ssh_password='GW2_pwd',
        ) as tunnel2:
            print('Connection to tunnel2 (GW2_ip:GW2_port) OK...')
            with SSHClient() as ssh:
                ssh.connect('localhost',
                    port=tunnel2.local_bind_port,
                    username='target_user',
                    password='target_pwd',
                )
                ssh.exec_command(...)
  7. Use SSHTunnelForwarder for manual tunnel management

    master

    The SSHTunnelForwarder class allows you to manually initialize, start, and stop an SSH tunnel. This is useful when you need to control the lifecycle of the tunnel explicitly. You can access the dynamically assigned local port via the local_bind_port attribute.

    from sshtunnel import SSHTunnelForwarder
    
    server = SSHTunnelForwarder(
        'alfa.8iq.dev',
        ssh_username="pahaz",
        ssh_password="secret",
        remote_bind_address=('127.0.0.1', 8080)
    )
    
    server.start()
    print(server.local_bind_port)  # show assigned local port
    # work with your service through server.local_bind_port
    server.stop()
  8. Use open_tunnel with a context manager

    master

    The open_tunnel function provides a convenient way to manage tunnels using a with statement. This ensures that the tunnel is automatically stopped when the block is exited. This method supports various authentication methods including passwords, private keys (ssh_pkey), and private key passwords (ssh_private_key_password).

    import paramiko
    import sshtunnel
    
    # Example: Forwarding to a private server via a gateway
    with sshtunnel.open_tunnel(
        (REMOTE_SERVER_IP, 443),
        ssh_username="",
        ssh_pkey="/var/ssh/rsa_key",
        ssh_private_key_password="secret",
        remote_bind_address=(PRIVATE_SERVER_IP, 22),
        local_bind_address=('0.0.0.0', 10022)
    ) as tunnel:
        client = paramiko.SSHClient()
        client.load_system_host_keys()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect('127.0.0.1', 10022)
        # do some operations with client session
        client.close()
  9. Reference: sshtunnel CLI options

    master

    Full list of available CLI options for sshtunnel:

    usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
                     IP:PORT [IP:PORT ...] [-L [IP:PORT ...]] [-k SSH_HOST_KEY]
                     [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v] [-V] [-x IP:PORT]
                     [-c SSH_CONFIG_FILE] [-z] [-n] [-d [FOLDER ...]]
                     ssh_address
    
    positional arguments:
      ssh_address           SSH server IP address (GW for SSH tunnels)
                            set with "-- ssh_address" if immediately after -R or -L
    
    options:
      -h, --help            show this help message and exit
      -U SSH_USERNAME, --username SSH_USERNAME
                            SSH server account username
      -p SSH_PORT, --server_port SSH_PORT
                            SSH server TCP port (default: 22)
      -P SSH_PASSWORD, --password SSH_PASSWORD
                            SSH server account password
      -R IP:PORT [IP:PORT ...], --remote_bind_address IP:PORT [IP:PORT ...]
                            Remote bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
                            Equivalent to ssh -Lxxxx:IP_ADDRESS:PORT
                            If port is omitted, defaults to 22.
      -L [IP:PORT ...], --local_bind_address [IP:PORT ...]
                            Local bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
                            Elements may also be valid UNIX socket domains:
                            /tmp/foo.sock /tmp/bar.sock ... /tmp/baz.sock
                            Equivalent to ssh -LPORT:xxxxxxxxx:xxxx, being the local IP address optional.
                            By default it will listen in all interfaces (0.0.0.0) and choose a random port.
      -k SSH_HOST_KEY, --ssh_host_key SSH_HOST_KEY
                            Gateway's host key
      -K KEY_FILE, --private_key_file KEY_FILE
                            RSA/DSS/ECDSA private key file
      -S KEY_PASSWORD, --private_key_password KEY_PASSWORD
                            RSA/DSS/ECDSA private key password
      -t, --threaded        Allow concurrent connections to each tunnel
      -v, --verbose         Increase output verbosity (default: ERROR)
      -V, --version         Show version number and quit
      -x IP:PORT, --proxy IP:PORT
                            IP and port of SSH proxy to destination
      -c SSH_CONFIG_FILE, --config SSH_CONFIG_FILE
                            SSH configuration file, defaults to ~/.ssh/config
      -z, --compress        Request server for compression over SSH transport
      -n, --noagent         Disable looking for keys from an SSH agent
      -d [FOLDER ...], --host_pkey_directories [FOLDER ...]
                            List of directories where SSH pkeys (in the format `id_*`) may be found
  10. Use the sshtunnel CLI

    master

    The sshtunnel command-line interface allows you to create tunnels directly from the terminal.

    Basic Syntax:

    sshtunnel [options] ssh_address

    Common Flags:

    • -U, --username SSH_USERNAME: SSH server account username.
    • -p, --server_port SSH_PORT: SSH server TCP port (default: 22).
    • -P, --password SSH_PASSWORD: SSH server account password.
    • -R IP:PORT: Remote bind address (equivalent to ssh -L). If port is omitted, defaults to 22.
    • -L [IP:PORT]: Local bind address (equivalent to ssh -L). Defaults to listening on 0.0.0.0 on a random port.
    • -K, --private_key_file KEY_FILE: RSA/DSS/ECDSA private key file.
    • -S, --private_key_password KEY_PASSWORD: Private key password.
    • -c, --config SSH_CONFIG_FILE: Path to SSH configuration file (defaults to ~/.ssh/config).
    • -t, --threaded: Allow concurrent connections to each tunnel.
    • -v, --verbose: Increase output verbosity.

    Example (Vagrant MySQL):

    python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
    sshtunnel --help
  11. Configure the MySQL database container

    master

    The mysqldb service provides a MySQL instance for testing, allowing configuration of the database name, user, and passwords via environment variables.

    mysqldb:
        image: mysql:8.0.33
        environment:
          MYSQL_DATABASE: main
          MYSQL_USER: mysql
          MYSQL_PASSWORD: mysql
          MYSQL_ROOT_PASSWORD: mysqlroot
        networks:
          inner:
            ipv4_address: 10.5.0.6
  12. Configure the internal test network

    master

    The inner network is a bridge network used to connect the SSH server and various database containers. It uses a specific IPAM (IP Address Management) configuration to assign static IPs to services.

    networks:
      inner:
        driver: bridge
        ipam:
         config:
           - subnet: 10.5.0.0/16
             gateway: 10.5.0.1