如何通过subprocess模块调用ssh以使用SSH_ASKPASS变量

9 投票
3 回答
21924 浏览
提问于 2025-04-15 16:19

我正在写一个图形界面程序,这个程序需要用到SSH命令。我尝试使用subprocess模块来调用ssh,并设置SSH_ASKPASS环境变量,这样我的应用就可以弹出一个窗口来询问SSH密码。但是我发现ssh总是在终端窗口里提示输入密码,不管我怎么设置DISPLAY、SSH_ASKPASS和TERM这些环境变量,也不管我怎么处理标准输入输出。我该怎么做才能让ssh和当前的终端分开,并使用指定的程序来读取密码呢?

我的测试代码是:

#!/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()

3 个回答

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

这样一来,你就可以通过ssh命令进行连接了(因为子进程似乎不能直接接受ssh命令)。你需要创建一个脚本(记得把它设置为可执行,比如用命令chmod 755 my_script.sh),在里面写上你想要的内容,比如:

#!/bin/bash
ssh user@other_machine ls

然后在你的程序中调用这个脚本:

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

如果是要在其他人的机器上部署的应用,我建议使用abyx的方法,利用SSH库来处理。这比搞一些环境变量要简单得多。

4

你的问题是SSH会检测到你的终端(TTY),并直接与它进行通信(这在手册中有明确说明)。你可以尝试在没有终端的情况下运行ssh——手册建议你可能需要把stdin重定向到/dev/null,这样ssh就会认为没有终端。

你也可以使用pexpect来实现这个功能,它与SSH兼容——可以参考这个使用示例

做你想做的事情的正确方法(TM)有两种:

  1. 使用专门为Python设计的SSH库(比如twisted conchparamiko
  2. 使用公钥和私钥,这样就不需要密码了
16

SSH只在进程完全与终端分离时使用SSH_ASKPASS变量(仅仅重定向标准输入和设置环境变量是不够的)。要让一个进程与控制台分离,它需要进行一次分叉,并调用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
)

撰写回答