pythonsshparamikossh-tunnelca

Python, SSH Tunnel forwarding with CA key


I had a working SSH tunnelforwarder using sshtunnel library.

ssh_tunnel_server = {
    'ssh_address_or_host': (remote_host, 22),
    'ssh_username': ssh_user,
    'ssh_pkey': ssh_key_path,
    'remote_bind_address': ("localhost", 5900),
    'local_bind_address': ('127.0.0.1', 0),
}
tunnel = SSHTunnelForwarder(**ssh_tunnel_server)

tunnel.start()

Unfortunately, I now need to cover also ssh connections signed by CAs.

Using paramiko directly I found out that a simple ssh connection with CAs can be established simply by providing the CAs public key as key_filename parameter. With open_session() I could also execute commands.

client.connect(ssh_host, port=ssh_port, username=ssh_user, pkey=private_key, key_filename=ssh_cert_path)

I tried to use the transport.request_port_forward function in any combination of ip address and port, but failed.

transport.request_port_forward("localhost", local_port,handler(channel, "127.0.0.1",remote_port))

It hangs at chan = transport.accept(1000).

Code is:

def forward_tunnel(local_port, remote_host, remote_port, transport: Transport):
    transport.request_port_forward("", local_port)
    while True:
        chan = transport.accept(1000)
        if chan is None:
            continue
        thr = threading.Thread(target=handler, args=(chan, remote_host, remote_port))
        thr.setDaemon(True)
        thr.start()

How to use properly the request port forward method?


SOLUTION:

First of all, thanks @Martin Prikryl for your help. Somehow when I used Transport.open_channel I tought I need to use forwarded-tcpip. So, I tried your approach and it finally worked.

Code:

def forward_tunnel(local_port, remote_host, remote_port, transport: Transport):
    channel = transport.open_channel("direct-tcpip",(remote_host,remote_port),("127.0.0.1",local_port))

    local_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    local_socket.bind(("127.0.0.1", local_port))
    local_socket.listen(1)
    conn, addr = local_socket.accept()
    timeout = 5

    try:
        while True:
            r, w, x = select.select([channel, conn], [], [], timeout)

            if channel in r:
                data = channel.recv(1024)
                if len(data) > 0:
                    conn.sendall(data)
                else:
                    break

            if conn in r:
                data = conn.recv(1024)
                if len(data) > 0:
                    channel.sendall(data)
                else:
                    break
    finally:
        channel.close()
        conn.close()
        local_socket.close()

Solution

  • The Transport.request_port_forward is for remote port forwarding.


    The sshtunnel.SSHTunnelForwarder is implementing local port forwarding. When implementing the local port forwarding, you need to be listening to (accepting) local connections. After accepting the connection, you need to call Transport.open_channel to create direct-tcpip channel. And forward all local connection data to/from the channel. Check the _ForwardHandler class in sshtunnel (you might be able to reuse it or at least its code).

    Though note that if you use the forwarding internally in your Python code, you do not need any _port_. You can just read from/write to the forwarding channel.