将子进程作为不同用户从长时间运行的Python进程运行

Pet*_*nte 43 python fork subprocess setuid

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

我正在使用

su -m nobody -c <program to execute as a child>
Run Code Online (Sandbox Code Playgroud)

但这似乎是重量级的,并不会非常干净地死去.

有没有办法以编程方式完成此操作而不是使用su?我正在查看os.set*uid方法,但Python std lib中的doc在该领域非常稀少.

Wal*_* IV 80

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

  1. 定义一个函数,用于设置正在运行的进程的gid和uid.
  2. 将此函数作为preexec_fn参数传递给subprocess.Popen

subprocess.Popen将使用fork/exec模型来使用preexec_fn.这相当于按顺序调用os.fork(),preexec_fn()(在子进程中)和os.exec()(在子进程中).由于os.setuid,os.setgid和preexec_fn都只在Unix上受支持,因此该解决方案无法移植到其他类型的操作系统.

以下代码是一个脚本(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()
Run Code Online (Sandbox Code Playgroud)

您可以像这样调用此脚本:

从根开始......

(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
Run Code Online (Sandbox Code Playgroud)

在子进程中成为非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
Run Code Online (Sandbox Code Playgroud)

当子进程退出时,我们回到父进程的根...

(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
Run Code Online (Sandbox Code Playgroud)

请注意,让父进程等待子进程退出仅用于演示目的.我这样做是为了让父母和孩子共享一个终端.一个守护进程没有终端,很少等待子进程退出.

  • 注意上面的例子只使用用户的**主要gid**,如果你想使用用户组的**ALL**,那么你可以使用**os.initgroups(user_name,user_gid)**而不是os.setgid.这需要将用户名传递给demote() (5认同)
  • 这可能很明显,(不适合我),但是......你应该*首先更改`gid`,如示例所示! (4认同)

Pra*_*ush 16

新版本的 Python(3.9 及以上)支持user开箱即用的group选项:

process = subprocess.Popen(args, user=username)
Run Code Online (Sandbox Code Playgroud)

新版本还提供了一个subprocess.run功能。它是一个简单的包装subprocess.Popensuprocess.Popen在后台运行命令时,subprocess.run运行命令等待其完成。

因此我们还可以这样做:

subprocess.run(args, user=username)
Run Code Online (Sandbox Code Playgroud)


Emi*_*nov 11

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

一个解决方案是,在孩子开始的地方,调用os.setuid()os.setgid()更改用户和组ID,然后调用其中一个os.exec*方法来生成一个新子项.新产生的孩子将与功能较弱的用户一起运行,而无法再次成为更强大的用户.

另一种方法是在守护进程(主进程)启动时执行此操作,然后所有新生成的进程将在同一用户下运行.

有关信息,请查看setuid联机帮助页.

  • 如果您切换到具有其他gid的用户,您可能还需要`os.setgroups()`.除此之外,是的,它非常直截了当. (3认同)
  • subprocess.Popen函数有一个preexec_func参数,可用于完成Emil建议的双子生成.preexec_func可以在第一个启动的子进程的上下文中调用os.setgid和os.setuid,然后将以该用户身份启动第二个子进程. (2认同)
  • 在Linux上不是这样.设置有效的UID,然后在完成后将其设置回真实用户ID. (2认同)