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

3 投票
1 回答
8806 浏览
提问于 2025-04-18 12:53

这里有一些相关的问题,它们基本上在问同样的事情,但给出的答案对我没有用:

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

如何使用subprocess模块与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=”,添加或去掉subprocess.PIPE的赋值给stdoutstderr。但是,结果总是出现相同的提示:

ubuntu@<ipv6-link-local-addr>%eth0's password:

我是不是漏掉了什么?或者有没有其他方法可以使用Python的标准库来实现这个功能?

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>

撰写回答