简体   繁体   English

与Paramiko嵌套的SSH会话

[英]Nested SSH session with Paramiko

I'm rewriting a Bash script I wrote into Python. 我正在重写我写到Python中的Bash脚本。 The crux of that script was 该脚本的关键是

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

I'm having a problem with the nested authentication with paramiko. 我在使用paramiko进行嵌套身份验证时遇到问题。 I wasn't able to find any examples dealing with my precise situation, but I was able to find examples with sudo on a remote host. 我找不到适合我具体情况的任何示例,但是我能够在远程主机上找到带有sudo的示例。

The first method writes to stdin 第一种方法写入标准输入

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 creates a channel and uses the socket-like send and recv . 第二个创建通道并使用类似于套接字的sendrecv

I was able to get stdin.write to work with sudo , but it doesn't work with ssh on the remote host. 我能够使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()

...prints... ...印刷品...

---- 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']

The pseudo-terminal error reminded me of the -t flag in my original command, so I switched to the second method, using a Channel. 伪终端错误使我想起了原始命令中的-t标志,因此我使用Channel切换到第二种方法。 Instead of ssh.exec_command and later, I have: 我没有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)

...but it prints '---- send ssh cmd ----' and just hangs until I kill the process. ...但是它会打印'---- send ssh cmd ----'并挂起,直到我终止该进程。

I'm new to Python and none too knowledgeable about networks. 我是Python的新手,对网络的了解也不多。 In the first case, why does sending the password work with sudo but not with ssh ? 在第一种情况下,为什么发送密码只能用于sudo但不能用于ssh Are the prompts different? 提示是否不同? Is paramiko even the right library for this? paramiko甚至是正确的库吗?

I managed to find a solution, but it requires a little manual work. 我设法找到一个解决方案,但是需要一些手动工作。 If anyone have a better solution, please tell me. 如果有人有更好的解决方案,请告诉我。

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()

The thing to note is that instead of this 需要注意的是

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

...you want this ...你要这个

chan = ssh.invoke_shell()

It reminds me of when I tried to write a TradeWars script when I was a kid and gave up coding for ten years. 这让我想起了我小时候放弃编写代码十年的尝试编写TradeWars脚本的过程。 :) :)

Here is a small example using paramiko only (and port forwarding): 这是一个仅使用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')

You can create ssh connection using channel from another ssh connection. 您可以使用来自另一个ssh连接的通道来创建ssh连接。 See here for more detail. 有关更多详细信息,请参见此处

For a ready made solution check out pxssh from the pxpect project. 要获得现成的解决方案,请从pxpect项目中检查pxssh。 Look at the sshls.py and ssh_tunnel.py examples. 查看sshls.py和ssh_tunnel.py示例。

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

Sinas's answer works well but didn't provide all the output from very long commands for me. Sinas的答案效果很好,但是没有为我提供很长命令的所有输出。 However, using chan.makefile() allows me to retrieve all the output. 但是,使用chan.makefile()可以检索所有输出。

The below works on a system that requires tty and also prompts for sudo password 以下内容适用于需要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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM