pythonparamikossh-tunnel

How to run a command over an ssh tunnel?


I'm trying to automate launches of VNC-clients for multiple remote systems. Currently I achieve that with a LocalForward 15900 127.0.0.1:5900. VNC uses port 5900 by default, which is fine -- there should only be one VNC-server at a time on any machine. But I may some times have multiple VNC-viewers running, so the local port must be dynamic -- and cannot be hardcoded (such as to 15900).

To make it nicer, I'd like to implement a script, which will:

  1. Find an unused local port (freePort).
  2. SSH into the specified remote computer (as the specified user).
  3. Create a tunnel from the remote 5900 to the local freePort.
  4. Invoke the x11vnc -xkb -display :0 -localhost remotely.
  5. Invoke the vncviewer localhost:freePort locally.

The first item I do using this method. I then use SSHTunnelForwarder:

import os
import paramiko
import socket
import sys
import sshtunnel
import logging
from paramiko import SSHClient 

def freePort():
    s = socket.socket()
    s.bind(('localhost', 0))
    port = s.getsockname()[1]
    s.close()
    return port

logging.basicConfig(level = logging.DEBUG,
    format='%(asctime)s %(message)s')

try:
    user, host = sys.argv[1].split('@')
except ValueError:
    host = sys.argv[1]
    user = os.getlogin()

tunnel = sshtunnel.SSHTunnelForwarder(host, ssh_username = user,
    local_bind_address = ('127.0.0.1', freePort()),
    remote_bind_address = ('127.0.0.1', 5900))
print(tunnel)

The above works nicely and prints the following at the end:

<class 'sshtunnel.SSHTunnelForwarder'> object
ssh gateway: foo.example.com:23
proxy: no
username: meow
authentication: {'pkeys': [('ssh-rsa', b'081c323ca3b5bb6f157f91984b2cb7b2')]}
hostkey: not checked
status: not started
keepalive messages: disabled
tunnel connection check: disabled
concurrent connections: allowed
compression: not requested
logging level: ERROR
local binds: [('127.0.0.1', 21242)]
remote binds: [('127.0.0.1', 5900)]

But now I need to launch the remote command (step 4.)... Presumably, I need to use Paramiko's SSHClient() for that, but I don't see, how to do that without starting a whole new ssh-connection. There's got to be a way to issue a command over the already-created tunnel. How would I do that?

Also: how would I insist on the remote hostkey being verified -- against the known_hosts-database?


Solution

  • Ended up using the actual ssh-client as below. Would've liked a Python-solution...

    import os
    import socket
    import sys
    import time
    
    def freePort():
        s = socket.socket()
        s.bind(('localhost', 0))
        port = s.getsockname()[1]
        s.close()
        return port
    
    port = freePort()
    os.system("ssh -f -n -L %d:localhost:5900 %s exec x11vnc -xkb -display :0 -localhost" %
        (port, sys.argv[1]))
    time.sleep(2)   # Remote takes a few seconds to get ready
    os.system("exec env LANG=C vncviewer localhost:%d" % port)