如何通过子进程模块调用 ssh 以便它使用 SSH_ASKPASS 变量

Posted

技术标签:

【中文标题】如何通过子进程模块调用 ssh 以便它使用 SSH_ASKPASS 变量【英文标题】:How to call ssh by subprocess module so that it uses SSH_ASKPASS variable 【发布时间】:2010-12-19 17:26:17 【问题描述】:

我正在编写一个使用 SSH 命令的 GUI。我尝试使用 subprocess 模块调用 ssh 并设置 SSH_ASKPASS 环境变量,以便我的应用程序可以弹出一个窗口询问 SSH 密码。但是,我无法使用给定的 SSH_ASKPASS 命令让 ssh 读取密码:它总是在终端窗口中提示它,无论我如何设置 DISPLAY、SSH_ASKPASS、TERM 环境变量或如何通过管道传输标准输入/输出。如何确保 ssh 与当前 TTY 分离并使用给定程序读取密码?

我的测试代码是:

#!/usr/bin/env python

import os
import subprocess

env = dict(os.environ)
env['DISPLAY'] = ':9999' # Fake value (trying in OS X and Windows)
del env['TERM']
env['SSH_ASKPASS'] = '/opt/local/libexec/git-core/git-gui--askpass'

p = subprocess.Popen(['ssh', '-T', '-v', 'user@myhost.com'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    env=env
)
p.communicate()

【问题讨论】:

SSH 将始终在调用它的子进程中提示您,因此您需要做的是告诉Popen() 使用p.stdin.write() 发送密码,或者只是简化整个该死的事情并使用一些东西用于已经为您编写的 SSH 交互,例如 Paramiko。 感谢您的回答。我尝试使用 p.stdin.write() 和 p.communicate() 发送密码,但是 ssh 仍然使用实际的 TTY 来读取密码……另一个问题是当 SSH 要求输入密码时不容易检测到(它可以使用不同的字符串来完成,这些字符串可以稍后出现在 SSH 会话等中) 看到您愿意自己将密码“写入”到 SSH 进程,我已经更新了我的答案,选择使用 pexcpet,这将适用。 【参考方案1】:

如果您想要一种快速而肮脏的方式来为您自己的个人使用,您可以通过在您的终端中执行以下操作来启用这两台机器之间的无密码登录:

ssh-keygen -t rsa # generate a keypair (if you haven't done this already)
ssh-copy-id user@other_machine # copy your public key to the other machine

然后你可以通过创建一个脚本(记住将其标记为可执行,例如chmod 755 my_script.sh)来获取ssh命令(子进程似乎不能直接接受ssh命令)你想要的东西,例如:

#!/bin/bash
ssh user@other_machine ls

并从您的程序中调用它:

import subprocess
response = subprocess.call("./my_script.sh")
print(response)

对于需要部署在其他人机器上的应用程序的生产用途,我会采用 abyx 使用 SSH 库的方法。比弄乱一些环境变量要简单得多。

【讨论】:

【参考方案2】:

SSH 仅在进程真正与 TTY 分离时才使用 SSH_ASKPASS 变量(stdin 重定向和设置环境变量是不够的)。要从控制台分离一个进程,它应该分叉并调用 os.setsid()。所以我找到的第一个解决方案是:

# Detach process
pid = os.fork()
if pid == 0:
    # Ensure that process is detached from TTY
    os.setsid()

    # call ssh from here
else:
    print "Waiting for ssh (pid %d)" % pid
    os.waitpid(pid, 0)    
    print "Done"

使用 subprocess 模块还有一种优雅的方法:在 preexec_fn 参数中,我们可以传递一个 Python 函数,该函数在执行外部命令之前在子进程中调用。所以这个问题的解决方案是多一行:

env = 'SSH_ASKPASS':'/path/to/myprog', 'DISPLAY':':9999'
p = subprocess.Popen(['ssh', '-T', '-v', 'user@myhost.com'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    env=env,
    preexec_fn=os.setsid
)

【讨论】:

这正是我所追求的。你是我的英雄,先生。【参考方案3】:

您的问题是 SSH 检测到您的 TTY 并直接与其对话(如手册页中明确说明的那样)。您可以尝试在没有终端的情况下运行 ssh - 手册页建议可能需要将 stdin 重定向到 /dev/null 以使 ssh 认为它没有终端。

您也可以为此使用pexcept,众所周知,它可以与 SSH 一起使用 - 例如 usage。

正确的方式 (TM) 做你想做的事是:

    使用专门用于在 python 中使用 SSH 的库(例如 twisted conch 或 paramiko) 使用公钥和私钥,这样就不需要密码

【讨论】:

正如您在代码中看到的那样,我正在重定向标准输入,并且我没有向它发送任何数据,所以它与我将标准输入重定向到 / 完全一样开发/空。还是我错了?我很确定这个问题可以在不使用完整的 ssh 实现的情况下解决:例如,可以从终端运行 git-gui(用 tcl/tk 编写)。它在后台运行 ssh,并使用自己的程序(git-gui--askpass)询问密码。 我还尝试在调用 Popen 后立即关闭标准输入,以确保代码模拟 /dev/null 行为。没有成功:( 我同意 PKI 会简单得多,但是:1. 我不能强制所有用户使用基于公钥的身份验证,2. 如果私钥受密码保护,我仍然需要一些密码对话框支持...(不,不确定每个用户在启动我的程序之前是否已经将密钥添加到其 ssh-agent...)

以上是关于如何通过子进程模块调用 ssh 以便它使用 SSH_ASKPASS 变量的主要内容,如果未能解决你的问题,请参考以下文章

使用python子进程和ssh读取远程文件?

如何从 Python 中的子进程向 SSH 传递命令

使用 Python 子进程的 ssh 多个命令

通过ssh或https自动访问git子模块

text 自动操作.gitmodules,以便Travis CI从公共URL而不是SSH URL中提取子模块。

Python 子进程 - 通过 SSH 运行多个 shell 命令