0

Запуск дочерних процессов от имени другого пользователя из долгосрочного Python-процесса

9

У меня есть долгосрочный демонизированный процесс на Python, который использует subprocess для создания новых дочерних процессов при возникновении определенных событий. Долгосрочный процесс запускается пользователем с привилегиями суперпользователя, и мне нужно, чтобы дочерние процессы выполнялись от имени другого пользователя (например, "nobody"), при этом родительский процесс сохранял привилегии суперпользователя.

В данный момент я использую следующую команду:

su -m nobody -c <программа для выполнения как дочерний процесс>

Однако это выглядит громоздко и завершает работу не очень аккуратно.

Есть ли способ сделать это программно, без использования su? Я рассматриваю методы os.set*uid, но документация в стандартной библиотеке Python по этому вопросу довольно скудная.

2 ответ(ов)

1

Судя по тому, что вы упомянули демона, можно заключить, что вы работаете на 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

Когда дочерний процесс завершится, мы обратно перейдем к root в родительском процессе...

(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

Примечание: ожидание завершения дочернего процесса родительским процессом используется только в демонстрационных целях. Я сделал это, чтобы родитель и ребенок могли делить терминал. Демон же обычно не имеет терминала и редко ждет завершения дочернего процесса.

0

Конечно, вот перевод на русский в стиле ответа на StackOverflow:


На самом деле пример с preexec_fn у меня не сработал.

Мое решение, которое хорошо работает для выполнения некоторых shell-команд от имени другого пользователя и получения их вывода, выглядит следующим образом:

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

Если вам нужно читать вывод процесса из стандартного вывода, вы можете сделать это так:

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

Надеюсь, это будет полезно не только в моем случае.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь