使用Python Paramiko实现SSH嵌套

24

我有这样的情况:

本地主机 --------- 跳板主机 ------- 目标机器

我正在尝试使用Paramiko在Python中编写代码,首先从本地主机到跳板主机进行SSH,然后从跳板主机到目标机器进行SSH。 从目标机器上,我想捕获一些输出,并将它们存储在本地,可以作为变量或文件(还没有到那个点)。 我在Stack Overflow上找到了一个示例,讲解如何使用Paramiko进行嵌套SSH,我按照示例进行操作,但卡在这里:

我的代码:

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
当我运行上述代码时,出现了以下错误:
$ 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个回答

31
尝试使用以下修改后的代码,它应该可以正常工作:
#!/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

为了解释代码中的所有地址和端口,以下是一些指示:1)local_addr 应该是本地 IP 和端口。不是服务器 A 的 IP 和端口。但实际上这个值并没有真正被使用。例如,PuTTY 进行端口转发时会传递 '0.0.0.0'0。2)在 jhost.connect 中的 '10.103.53.26' 也没有被真正使用。它可能只用于主机密钥验证,但这段代码通过使用 AutoAddPolicy 来明确跳过此步骤(这是错误的 - 对于正确的解决方案,请参见 Paramiko “Unknown Server”)。 - Martin Prikryl

6

我知道OP特别要求使用Paramiko,但是我可以很容易地使用fabric实现这个功能。以下是我的解决方案

from fabric import Connection

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

这对我来说很好用,我也将输出存储在变量中了。

在尝试了其他解决方案一个小时左右后,我只花了几分钟就让它正常工作了。非常感谢您的建议! - Christopher Bottoms
当使用fabric时,如何设置网关/跳板主机,就像问题中所述? - Alphy13

2

阅读了被接受的答案https://dev59.com/2lsW5IYBdhLWcg3wKkne#36096801后,对源地址和目标地址感到有些困惑,因此参考了https://www.programcreek.com/python/?code=grycap%2Fim%2Fim-master%2FIM%2FSSH.py,这是我最终得出的可行解决方案:

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)

感谢您提供脚本,我很感激。我有一个问题,我收到了“Exception: SSHException: Error reading SSH protocol banner”异常。您是否遇到过这个问题?如果是,您是如何解决的?我已经尝试增加“banner_timeout”,但没有成功。 - DataBach
@DataBach:我记得以前没有遇到过这样的异常。这个链接可能会有所帮助 https://dev59.com/G18e5IYBdhLWcg3wlbFR。 - merlachandra

2

我发现通过跳板机登录远程服务器的最简单方法。这种方法非常棒!

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接