以与长时间运行的 Python 进程不同的用户身份运行子进程

发布于 2024-08-11 21:47:05 字数 328 浏览 8 评论 0原文

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

我目前正在使用,

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

但这看起来很重量级,并且死得不是很干净。

有没有办法以编程方式完成此操作而不是使用 su?我正在查看 os.set*uid 方法,但 Python 标准库中的文档在该区域非常稀疏。

I've got a long running, daemonized Python process that uses subprocess to spawn new child processes when certain events occur. The long running process is started by a user with super user privileges. I need the child processes it spawns to run as a different user (e.g., "nobody") while retaining the super user privileges for the parent process.

I'm currently using

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

but this seems heavyweight and doesn't die very cleanly.

Is there a way to accomplish this programmatically instead of using su? I'm looking at the os.set*uid methods, but the doc in the Python std lib is quite sparse in that area.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

断桥再见 2024-08-18 21:47:05

既然你提到了守护进程,我可以断定你正在类 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()

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

以 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

在子进程中成为非 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

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

(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

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

Since you mentioned a daemon, I can conclude that you are running on a Unix-like operating system. This matters, because how to do this depends on the kind operating system. This answer applies only to Unix, including Linux, and Mac OS X.

  1. Define a function that will set the gid and uid of the running process.
  2. Pass this function as the preexec_fn parameter to subprocess.Popen

subprocess.Popen will use the fork/exec model to use your preexec_fn. That is equivalent to calling os.fork(), preexec_fn() (in the child process), and os.exec() (in the child process) in that order. Since os.setuid, os.setgid, and preexec_fn are all only supported on Unix, this solution is not portable to other kinds of operating systems.

The following code is a script (Python 2.4+) that demonstrates how to do this:

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()

You can invoke this script like this:

Start as 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

Become non-root in a child process...

(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

When the child process exits, we go back to root in parent ...

(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

Note that having the parent process wait around for the child process to exit is for demonstration purposes only. I did this so that the parent and child could share a terminal. A daemon would have no terminal and would seldom wait around for a child process to exit.

镜花水月 2024-08-18 21:47:05

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

process = subprocess.Popen(args, user=username)

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

因此我们还可以这样做:

subprocess.run(args, user=username)

The new versions of Python (3.9 onwards) support user and group option out of the box:

process = subprocess.Popen(args, user=username)

The new versions also provide a subprocess.run function. It is a simple wrapper around subprocess.Popen. While suprocess.Popen runs the commands in the background, subprocess.run runs the commands and wait for their completion.

Thus we can also do:

subprocess.run(args, user=username)
巴黎夜雨 2024-08-18 21:47:05

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

一种解决方案是,在子进程启动的地方,调用 os.setuid() 和 os.setgid() 来更改用户和组 ID,然后调用其中之一os.exec* 方法来生成一个新的子进程。新生成的子级将与较弱的用户一起运行,而无法再次成为更强大的用户。

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

有关信息,请参阅 setuid 手册页

There is an os.setuid() method. You can use it to change the current user for this script.

One solution is, somewhere where the child starts, to call os.setuid() and os.setgid() to change the user and group id and after that call one of the os.exec* methods to spawn a new child. The newly spawned child will run with the less powerful user without the ability to become a more powerful one again.

Another is to do it when the daemon (the master process) starts and then all newly spawned processes will have run under the same user.

For information look at the manpage for setuid.

和我恋爱吧 2024-08-18 21:47:05

实际上, preexec_fn 的示例对我不起作用。
我的解决方案可以很好地从另一个用户运行一些 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

希望,它不仅在我的情况下很有用。

Actually, example with preexec_fn did not work for me.
My solution that is working fine to run some shell command from another user and get its output is:

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

Then, if you need to read from the process stdout:

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

Hope, it is useful not only in my case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文