从长时间运行的进程中以不同的用户身份运行subprocess

我有一个长期运行的守护进程的Python进程,它在某些事件发生时使用subprocess产生新的subprocess。 长时间运行的过程由拥有超级用户权限的用户启动。 我需要subprocess产生作为不同的用户(例如“nobody”)运行,同时保留父进程的超级用户权限。

我目前正在使用

su -m nobody -c <program to execute as a child> 

但这似乎重量级,并不会很干净地死去。

有没有办法做到这一点编程而不是使用苏? 我正在查看os.set * uid方法,但是Python std lib中的文档在该区域相当稀疏。

既然你提到了一个守护进程,我可以断定你正在运行一个类Unix的操作系统。 这很重要,因为如何做到这一点取决于那种操作系统。 这个答案适用于Unix ,包括Linux和Mac OS X.

  1. 定义一个函数来设置正在运行的进程的gid和uid。
  2. 将此函数作为preexec_fnparameter passing给subprocess.Popen

subprocess.Popen将使用fork / exec模型来使用你的preexec_fn。 这相当于按顺序调用os.fork(),preexec_fn()(在subprocess中)和os.exec()(在subprocess中)。 由于os.setuid,os.setgid和preexec_fn都只在Unix上受支持,所以此解决scheme不能移植到其他types的操作系统。

以下代码是演示如何执行此操作的脚本(Python 2.4 +):

 import os import pwd import subprocess import sys def main(my_args=None): if my_args is None: my_args = sys.argv[1:] user_name, cwd = my_args[:2] args = my_args[2:] pw_record = pwd.getpwnam(user_name) user_name = pw_record.pw_name user_home_dir = pw_record.pw_dir user_uid = pw_record.pw_uid user_gid = pw_record.pw_gid env = os.environ.copy() env[ 'HOME' ] = user_home_dir env[ 'LOGNAME' ] = user_name env[ 'PWD' ] = cwd env[ 'USER' ] = user_name report_ids('starting ' + str(args)) process = subprocess.Popen( args, preexec_fn=demote(user_uid, user_gid), cwd=cwd, env=env ) result = process.wait() report_ids('finished ' + str(args)) print 'result', result def demote(user_uid, user_gid): def result(): report_ids('starting demotion') os.setgid(user_gid) os.setuid(user_uid) report_ids('finished demotion') return result def report_ids(msg): print 'uid, gid = %d, %d; %s' % (os.getuid(), os.getgid(), msg) if __name__ == '__main__': main() 

你可以像这样调用这个脚本:

以root身份启动…

 (hale)/tmp/demo$ sudo bash --norc (root)/tmp/demo$ ls -l total 8 drwxr-xr-x 2 hale wheel 68 May 17 16:26 inner -rw-r--r-- 1 hale staff 1836 May 17 15:25 test-child.py 

在subprocess中变成非root用户

 (root)/tmp/demo$ python test-child.py hale inner /bin/bash --norc uid, gid = 0, 0; starting ['/bin/bash', '--norc'] uid, gid = 0, 0; starting demotion uid, gid = 501, 20; finished demotion (hale)/tmp/demo/inner$ pwd /tmp/demo/inner (hale)/tmp/demo/inner$ whoami hale 

当subprocess退出时,我们回到父进程的根目录…

 (hale)/tmp/demo/inner$ exit exit uid, gid = 0, 0; finished ['/bin/bash', '--norc'] result 0 (root)/tmp/demo$ pwd /tmp/demo (root)/tmp/demo$ whoami root 

请注意 ,让父进程等待subprocess退出仅用于演示目的 。 我这样做,使父母和孩子可以共享一个terminal。 守护进程没有terminal,很less会等待subprocess退出。

有一个os.setuid()方法。 您可以使用它来更改此脚本的当前用户。

一种解决scheme是,在孩子开始的某个地方,调用os.setuid()os.setgid()来更改用户和组ID,然后调用os.exec *方法之一来产生一个新的孩子。 新产生的孩子将会与不那么强大的用户一起运行,而不能再成为一个更强大的用户。

另一种方法是当守护进程(主进程)启动,然后所有新产生的进程将在同一用户下运行。

有关信息,请查看setuid的手册页 。

其实,preexec_fn的例子不适合我。
我的解决scheme正常工作,从另一个用户运行一些shell命令,并得到它的输出是:

 apipe=subprocess.Popen('sudo -u someuser /execution',shell=True,stdout=subprocess.PIPE) 

那么,如果你需要从stdout进程读取:

 cond=True while (cond): line=apipe.stdout.getline() if (....): cond=False 

希望,这不仅对我有用。

在sudo上启用用户,因为不需要密码

 username ALL=(ALL) NOPASSWD: ALL 

然后用sudo调用root函数,例如:

 import pexpect child = pexpect.spawn('sudo apachectl restart') for i in child: print i #if you want to see the output from the process