【问题标题】:Nested SSH session with Paramiko与 Paramiko 的嵌套 SSH 会话
【发布时间】:2010-12-27 01:40:27
【问题描述】:

我正在重写一个我写成 Python 的 Bash 脚本。该脚本的关键是

ssh -t first.com "ssh second.com very_remote_command"

我在使用 paramiko 进行嵌套身份验证时遇到问题。我无法找到任何处理我的确切情况的示例,但我能够在远程主机上找到使用 sudo 的示例。

The first method 写入标准输入

ssh.connect('127.0.0.1', username='jesse', password='lol')
stdin, stdout, stderr = ssh.exec_command("sudo dmesg")
stdin.write('lol\n')
stdin.flush()

The second 创建一个通道并使用类似套接字的 sendrecv

我能够让 stdin.writesudo 一起工作,但它不能与远程主机上的 ssh 一起工作。

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('first.com', username='luser', password='secret')
stdin, stdout, stderr = ssh.exec_command('ssh luser@second.com')
stdin.write('secret')
stdin.flush()
print '---- out ----'
print stdout.readlines()
print '---- error ----'
print stderr.readlines()

ssh.close()

...打印...

---- out ----
[]
---- error ----
['Pseudo-terminal will not be allocated because stdin is not a terminal.\r\n', 'Permission denied, please try again.\r\n', 'Permission denied, please try again.\r\n', 'Permission denied (publickey,password,keyboard-interactive).\r\n']

伪终端错误让我想起了原始命令中的 -t 标志,所以我切换到第二种方法,使用 Channel。而不是 ssh.exec_command 及更高版本,我有:

t = ssh.get_transport()
chan = t.open_session()
chan.get_pty()
print '---- send ssh cmd ----'
print chan.send('ssh luser@second.com')
print '---- recv ----'
print chan.recv(9999)
chan = t.open_session()
print '---- send password ----'
print chan.send('secret')
print '---- recv ----'
print chan.recv(9999)

...但它会打印 '---- send ssh cmd ----' 并一直挂起,直到我终止进程。

我是 Python 新手,对网络一无所知。在第一种情况下,为什么发送密码可以使用 sudo 而不是 ssh?提示不同吗? paramiko 是否适合这个库?

【问题讨论】:

    标签: python ssh paramiko


    【解决方案1】:

    我设法找到了解决方案,但它需要一些手动工作。如果有人有更好的解决方案,请告诉我。

    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect('first.com', username='luser', password='secret')
    
    chan = ssh.invoke_shell()
    
    # Ssh and wait for the password prompt.
    chan.send('ssh second.com\n')
    buff = ''
    while not buff.endswith('\'s password: '):
        resp = chan.recv(9999)
        buff += resp
    
    # Send the password and wait for a prompt.
    chan.send('secret\n')
    buff = ''
    while not buff.endswith('some-prompt$ '):
        resp = chan.recv(9999)
        buff += resp
    
    # Execute whatever command and wait for a prompt again.
    chan.send('ls\n')
    buff = ''
    while not buff.endswith('some-prompt$ '):
        resp = chan.recv(9999)
        buff += resp
    
    # Now buff has the data I need.
    print 'buff', buff
    
    ssh.close()
    

    需要注意的是,不是这个

    t = ssh.get_transport()
    chan = t.open_session()
    chan.get_pty()
    

    ...你想要这个

    chan = ssh.invoke_shell()
    

    这让我想起了我小时候尝试编写 TradeWars 脚本并放弃编码十年的情景。 :)

    【讨论】:

    • 我正在尝试将您的解决方案用于类似的需求。但是 ('some-prompt$ ') 并不总是固定的..它总是有一个 '#' 但其他一些内容会不断变化..我该如何解释它?
    • 如果以#结尾,则将some-prompt$ 改为#。但是,请确保检查尾随空格,即 ...endswith('# ')...endswith('#') 不同。
    • expect 实用程序(TCL 的东西)会帮助你吗?我相信 python 等价物是 pexpect...
    • 我在没有部署程序的地方工作时遇到了这个问题。我不得不通过堡垒主机从生产服务器中收集文件,以查看部署了什么。在过去的六年里,我一直在部署代码版本的地方工作,而不是完全免费的部署。 (人们会从任何版本的代码库中直接选择要部署的文件,并且每个人都有权这样做。)我不再需要嵌套 ssh 会话,因为我可以将我的脚本放在远程主机上并在那里运行它,如果我想要。
    • 我也更聪明。我很确定我可以使用 ssh 命令完成此操作。可能当时我不明白需要做的三个级别的转义。
    【解决方案2】:

    这是一个仅使用 paramiko(和端口转发)的小示例:

    import paramiko as ssh
    
    class SSHTool():
        def __init__(self, host, user, auth,
                     via=None, via_user=None, via_auth=None):
            if via:
                t0 = ssh.Transport(via)
                t0.start_client()
                t0.auth_password(via_user, via_auth)
                # setup forwarding from 127.0.0.1:<free_random_port> to |host|
                channel = t0.open_channel('direct-tcpip', host, ('127.0.0.1', 0))
                self.transport = ssh.Transport(channel)
            else:
                self.transport = ssh.Transport(host)
            self.transport.start_client()
            self.transport.auth_password(user, auth)
    
        def run(self, cmd):
            ch = self.transport.open_session()
            ch.set_combine_stderr(True)
            ch.exec_command(cmd)
            retcode = ch.recv_exit_status()
            buf = ''
            while ch.recv_ready():
                buf += ch.recv(1024)
            return (buf, retcode)
    
    # The example below is equivalent to
    # $ ssh 10.10.10.10 ssh 192.168.1.1 uname -a
    # The code above works as if these 2 commands were executed:
    # $ ssh -L <free_random_port>:192.168.1.1:22 10.10.10.10
    # $ ssh 127.0.0.1:<free_random_port> uname -a
    host = ('192.168.1.1', 22)
    via_host = ('10.10.10.10', 22)
    
    ssht = SSHTool(host, 'user1', 'pass1',
        via=via_host, via_user='user2', via_auth='pass2')
    
    print ssht.run('uname -a')
    

    【讨论】:

    • 您能否详细说明您要转发的端口以及在您的示例中执行转发的主机?
    • 在代码中添加了注释,并做了一个小改动。转发。
    • User1 和 pass1 用于目标,vía 用于第一个主机。谢谢新浪。
    【解决方案3】:

    您可以使用来自另一个 ssh 连接的通道创建 ssh 连接。详情请参阅here

    【讨论】:

    • 谢谢。这是我找到的最好的方法。
    • 这是最好的,应该标记为正确答案。
    【解决方案4】:

    对于现成的解决方案,请查看 pxpect 项目中的 pxssh。查看 sshls.py 和 ssh_tunnel.py 示例。

    http://www.noah.org/wiki/Pexpect

    【讨论】:

    • pexpect 现在在 Windows 上不受支持
    【解决方案5】:

    Sinas 的回答效果很好,但没有为我提供很长命令的所有输出。但是,使用 chan.makefile() 可以让我检索所有输出。

    以下适用于需要 tty 并提示输入 sudo 密码的系统

    ssh = paramiko.SSHClient()
    ssh.load_system_host_keys()
    ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
    ssh.connect("10.10.10.1", 22, "user", "password")
    chan=ssh.get_transport().open_session()
    chan.get_pty()
    f = chan.makefile()
    chan.exec_command("sudo dmesg")
    chan.send("password\n")
    print f.read()
    ssh.close()
    

    【讨论】:

      猜你喜欢
      • 2017-08-27
      • 2021-12-12
      • 2017-07-01
      • 2022-01-08
      • 2015-10-07
      • 1970-01-01
      • 2020-04-27
      相关资源
      最近更新 更多