与 Paramiko 的嵌套 SSH 会话
Posted
技术标签:
【中文标题】与 Paramiko 的嵌套 SSH 会话【英文标题】:Nested SSH session with Paramiko 【发布时间】: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 创建一个通道并使用类似套接字的 send 和 recv。
我能够让 stdin.write 与 sudo 一起工作,但它不能与远程主机上的 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 是否适合这个库?
【问题讨论】:
【参考方案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()
【讨论】:
以上是关于与 Paramiko 的嵌套 SSH 会话的主要内容,如果未能解决你的问题,请参考以下文章