将管道命令和sudo与Python子进程结合使用时的问题

时间:2018-10-03 16:54:38

标签: python subprocess

我正在尝试利用Python模块subprocess在Mac上自动执行终端命令。具体来说,我正在运行certain command在计算机上创建port mappings。但是,有问题的命令需要root特权和管道权限:

echo "
rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080
" | sudo pfctl -ef - 

为了通过subprocess将我的root密码传递给shell命令,我按照发现的here的代码示例在下面创建了一个脚本:

from subprocess import PIPE, Popen
p = Popen(['echo', '"rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080"\n'], stdin=PIPE, stderr=PIPE, universal_newlines=True)
p2 = Popen(['sudo', '-S']+['pfctl', '-ef', '-'], stdin=p.stdout, stderr=PIPE, universal_newlines=True) 
return p2.communicate('my_root_password\n')[1] 

请注意,要实现将echo输出传递到命令pfctl -ef -的管道,我创建了两个Popen对象,并将第一个对象的stdout传递给了stdin的第二个参数,如subprocess docs中的建议,并且正在使用Popen.communicate将root密码写入标准输入。

但是,我的上述脚本无法正常工作,因为在终端中仍然提示我输入我的root密码。奇怪的是,当使用不带管道的命令时,例如在运行sudo pfctl -s nat(以显示当前端口映射设置)时,我能够成功将root密码写入stdin:

p = Popen(['sudo', '-S']+'pfctl -s nat'.split(), stdin=PIPE, stderr=PIPE, universal_newlines=True)
print(p.communicate('root_password\n')[1])

上面的代码有效,因为显示的映射配置没有任何密码提示。

如何更改我的第一个Python脚本,以便在已经利用Popen.communicate将密码写入stdin的情况下不提示我输入root密码?


我正在macOS Sierra 10.12.5上运行此代码

1 个答案:

答案 0 :(得分:2)

我认为这只是管道未正确连接的简单情况。您无需为第一个流程的stdout指定管道,因此从外观上看,输出只是被打印到终端,然后流程结束。

第二个进程开始时,它将提示您输入密码,据我所知正确接收了该密码。但是,communicate方法然后关闭输入并等待该过程完成。据我所见,第一个进程的输出永远不会到达第二个进程,这就是为什么脚本无法正常工作的原因。除了创建单独的echo进程之外,为什么不直接使用communicate发送所需的所有文本数据?

您似乎遇到的另一个问题(我没有要检查的MAC)是sudo正在将提示直接打印到终端(即通过/dev/tty而不是{{1 }})。在我的stdout版本(在Debian上)上,添加sudo选项会使它将提示打印到-S。但是,看起来stderr选项在MAC上没有执行此操作。而是尝试使用-S禁用提示。

将所有内容放在一起,应该可以:

-p ''

安全说明

此答案已更新为不使用纯文本密码。请参阅下面的评论,以了解为什么这是一个坏主意的好例子!还要注意的是,使用Python将密码存储在内存中并不完全安全,就像将内存交换到磁盘一样,这将以纯文本形式包含密码。使用较低级别的语言,from subprocess import PIPE, Popen from getpass import getpass password = getpass() cmd = ['sudo', '-k', '-S', '-p', '', 'pfctl', '-ef', '-'] p = Popen(cmd, stdin=PIPE, stderr=PIPE, universal_newlines=True) text = password + '\n' text += 'rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080\n' p.communicate(text) 系统调用将用于防止任何包含密码的内存被交换。