使用标准Python库(而非pexpect)输入SSH密码

3

以下是相关问题,它们基本上询问的是同样的事情,但是它们的答案对我不起作用:

如何在运行csh脚本时让Python输入密码

如何使用子进程模块与SSH进行交互

如何使用Python远程执行进程

我想通过ssh连接到远程机器并运行一个命令。例如:

ssh <user>@<ipv6-link-local-addr>%eth0 sudo service fooService status

问题是我正在尝试使用仅标准库的Python脚本来完成此操作(没有pexpect)。我一直在尝试使用subprocess模块让它工作,但是当请求密码时,调用communicate始终会阻塞,即使我已将密码作为参数提供给communicate。例如:
proc = subprocess.Popen(
        [
            "ssh",
            "{testUser1}@{testHost1}%eth0".format(**locals()),
            "sudo service cassandra status"],
        shell=False,
        stdin=subprocess.PIPE)
a, b = proc.communicate(input=testPasswd1)
print "a:", a, "b:", b
print "return code: ", proc.returncode

我已经尝试了上述的几种变体(例如,删除“input=”,添加/删除对stdout和sterr的subprocess.PIPE分配)。然而,结果总是出现相同的提示:
ubuntu@<ipv6-link-local-addr>%eth0's password:

我漏掉了什么?还是有其他使用Python标准库来实现它的方法吗?

你试过尝试这个答案所提供的方法吗?在Linux环境下对我来说效果很好。如果你使用的是Windows系统,那么我想你可能就没有那么幸运了... - dano
你需要两次输入密码,一次是客户端的ssh密码,另一次是服务器端的sudo密码。我认为使用subprocess模块与ssh进行交互看起来很合理,并且可以修改sudo部分...你觉得哪里出了问题? - tdelaney
@tdelaney,服务器上没有sudo密码。似乎“如何交互…”解决方案可能有效,但我需要获取其中一个命令的输出,而简单的读取却没有返回任何内容。 - jonderry
1个回答

9

这个答案只是Torxed的这个答案的改编,我建议你去点赞。它仅增加了捕获在远程服务器上执行的命令的输出的能力。

import pty
from os import waitpid, execv, read, write

class ssh():
    def __init__(self, host, execute='echo "done" > /root/testing.txt', 
                 askpass=False, user='root', password=b'SuperSecurePassword'):
        self.exec_ = execute
        self.host = host
        self.user = user
        self.password = password
        self.askpass = askpass
        self.run()

    def run(self):
        command = [
                '/usr/bin/ssh',
                self.user+'@'+self.host,
                '-o', 'NumberOfPasswordPrompts=1',
                self.exec_,
        ]

        # PID = 0 for child, and the PID of the child for the parent    
        pid, child_fd = pty.fork()

        if not pid: # Child process
            # Replace child process with our SSH process
            execv(command[0], command)

        ## if we havn't setup pub-key authentication
        ## we can loop for a password promt and "insert" the password.
        while self.askpass:
            try:
                output = read(child_fd, 1024).strip()
            except:
                break
            lower = output.lower()
            # Write the password
            if b'password:' in lower:
                write(child_fd, self.password + b'\n')
                break
            elif b'are you sure you want to continue connecting' in lower:
                # Adding key to known_hosts
                write(child_fd, b'yes\n')
            else:
                print('Error:',output)

        # See if there's more output to read after the password has been sent,
        # And capture it in a list.
        output = []
        while True:
            try:
                output.append(read(child_fd, 1024).strip())
            except:
                break

        waitpid(pid, 0)
        return ''.join(output)

if __name__ == "__main__":
    s = ssh("some ip", execute="ls -R /etc", askpass=True)
    print s.run()

输出:

/etc:
adduser.conf
adjtime
aliases
alternatives
apm
apt
bash.bashrc
bash_completion.d
<and so on>

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