27

I have this scenario:

Local-host --------- jump-host ------- target-machine

I am trying to write a code in Python using Paramiko to first SSH from local-host to jump-host and then SSH from jump-host to the target-machine. From the target-machine, I want to capture some outputs and store them locally either as a variable or as a file (haven't got to that point yet). I found an example from Stack Overflow where they talk about using nested SSH with Paramiko, and I follow it but I get stuck here:

My code:

enter code here

#!/usr/bin/python
#
# Paramiko
#
import paramiko
import sys
import subprocess
#
# we instantiate a new object referencing paramiko's SSHClient class
#
vm=paramiko.SSHClient()
vm.set_missing_host_key_policy(paramiko.AutoAddPolicy())
vm.connect('192.168.115.103',username='osmanl',password='xxxxxx')
#
vmtransport = vm.get_transport()
dest_addr = ('192.168.115.103', 22)
local_addr = ('127.0.0.1', 22)
vmchannel = vmtransport.open_channel("direct-tcpip", dest_addr, local_addr)
#
jhost=paramiko.SSHClient()
jhost.set_missing_host_key_policy(paramiko.AutoAddPolicy())
jhost.load_host_keys('/home/osmanl/.ssh/known_hosts')
jhost.connect('10.103.53.26', username='latiu', password='xxxx', sock=vmchannel)
#
stdin, stdout, stderr = rtr.exec_command("show version | no-more")
#
print stdout.readline()
#
jhost.close()
vm.close()
# End

When I run the above, I get this error:

$ python sshvm.py
Traceback (most recent call last):
  File "sshvm.py", line 28, in <module>
    jhost.load_host_keys('/home/osmanl/.ssh/known_hosts')
  File "/usr/lib/python2.7/site-packages/paramiko-1.15.2-py2.7.egg/paramiko/client.py", line 121, in load_host_keys
    self._host_keys.load(filename)
  File "/usr/lib/python2.7/site-packages/paramiko-1.15.2-py2.7.egg/paramiko/hostkeys.py", line 94, in load
    with open(filename, 'r') as f:
IOError: [Errno 2] No such file or directory: '/home/osmanl/.ssh/known_hosts'

4 Answers 4

32

Try the following edited code, it should work:

#!/usr/bin/python
#
# Paramiko
#
import paramiko
#
# we instantiate a new object referencing paramiko's SSHClient class
#
vm = paramiko.SSHClient()
vm.set_missing_host_key_policy(paramiko.AutoAddPolicy())
vm.connect('192.168.115.103', username='osmanl', password='xxxxxx')
#
vmtransport = vm.get_transport()
dest_addr = ('10.103.53.26', 22) #edited#
local_addr = ('192.168.115.103', 22) #edited#
vmchannel = vmtransport.open_channel("direct-tcpip", dest_addr, local_addr)
#
jhost = paramiko.SSHClient()
jhost.set_missing_host_key_policy(paramiko.AutoAddPolicy())
#jhost.load_host_keys('/home/osmanl/.ssh/known_hosts') #disabled#
jhost.connect('10.103.53.26', username='latiu', password='xxxx', sock=vmchannel)
#
stdin, stdout, stderr = jhost.exec_command("show version | no-more") #edited#
#
print stdout.read() #edited#
#
jhost.close()
vm.close()
# End
Sign up to request clarification or add additional context in comments.

1 Comment

To shed some light on all the addresses and ports in the code: 1) local_addr should be local IP and port. Not server A's IP and port. But actually, the value is not really used. For example, PuTTY when doing port forwarding passes '0.0.0.0' and 0 there. 2) The '10.103.53.26' in the jhost.connect is not really used either. It's probably only used for host key verification, what this code explicitly skips by using AutoAddPolicy (what is wrong – for a correct solution, use For a correct solution, see Paramiko “Unknown Server”).
6

I know OP has specifically asked for Paramiko but i can do this very easily with fabric. Here is my solution

from fabric import Connection

out = Connection('host1').run('host2 uptime')
print(out.stdout.strip())

This works just fine for me, and i have the output stored in a variable as well.

2 Comments

After an hour or so spent on other solutions, I got this working in just minutes. Great recommendation!
How do you setup the gateway/jump host, as in the question, when using fabric?
2

I found this easiest route to login to remote server via jumphost.This works amazing!

    link :https://pypi.org/project/jumpssh/
    import jumpssh

Comments

2

After reading accepted answer got bit confused about source and destintation addresses https://stackoverflow.com/a/36096801/1303321, so refering to https://www.programcreek.com/python/?code=grycap%2Fim%2Fim-master%2FIM%2FSSH.py here is what I ended up with working solution:

def __run_remote_command(self, command: str) -> Tuple[str, str]:
        """
        Private method to run a command in the remote machine via SSH.
        This method establishes the connection; fires the command; collects the output then closes the connection
        :param command: command which needs to be invoked in the remote machine
        :return (stdout, stderr) : Tuple of string containing the standard output and error of the command execution
        """
        stdout, stderr = '', ''
        with paramiko.SSHClient() as jhost:
            jhost.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            private_key = paramiko.RSAKey.from_private_key_file(filename=RESOURCES_SERVER_SSH_KEY)
            try:
                jhost.connect(hostname=JUMPHOST_SERVER_URL, username=RESOURCES_SERVER_SSH_USERNAME, pkey=private_key)
                jhost_transport = jhost.get_transport()
                dest_addr = (RESOURCES_SERVER_URL, 22)
                local_addr = (JUMPHOST_SERVER_URL, 22)
                jhost_channel = jhost_transport.open_channel("direct-tcpip", dest_addr, local_addr)

                with paramiko.SSHClient() as target_server:
                    target_server.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                    target_server_private_key = paramiko.RSAKey.from_private_key_file(filename=RESOURCES_SERVER_SSH_KEY)
                    target_server.connect(hostname=RESOURCES_SERVER_URL, username=RESOURCES_SERVER_SSH_USERNAME, pkey=target_server_private_key, sock=jhost_channel)

                    self.logger.info(f"Invoking {command} on remote host {RESOURCES_SERVER_URL} over SSH")
                    _, stdout, stderr = target_server.exec_command(command)
                    stdout = stdout.read().decode('utf-8')
                    stderr = stderr.read().decode('utf-8')
            except SSHException as ssh_ex:
                self.logger.error(f"Failed to connect to {RESOURCES_SERVER_URL} ")
                self.logger.exception(ssh_ex, exc_info=True)
                raise BaseException()
        return (stdout, stderr)

2 Comments

Thank you for providing the script. I appreciate it. Question, I receive Exception: SSHException: Error reading SSH protocol banner exception. Did you encounter it? If yes how did you solve? I already tried to increase banner_timeout but no success.
@DataBach: No, I didn't encounter such an exception ever AFAIR. This may help stackoverflow.com/questions/25609153/….

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.