Подтвердить что ты не робот

Запуск дочерних процессов как разных пользователей из долгого процесса

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

В настоящее время я использую

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

но это кажется тяжеловесом и не умирает очень чисто.

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

4b9b3361

Ответ 1

Поскольку вы упомянули демона, я могу заключить, что вы работаете в Unix-подобной операционной системе. Это важно, потому что, как это сделать, это зависит от операционной системы. Этот ответ относится только к Unix, включая Linux и Mac OS X.

  • Определите функцию, которая установит gid и uid текущего процесса.
  • Передайте эту функцию в качестве параметра preexec_fn для подпроцесса .Popen

subprocess.Popen будет использовать модель fork/exec для использования вашего preexec_fn. Это эквивалентно вызову os.fork(), preexec_fn() (в дочернем процессе) и os.exec() (в дочернем процессе) в этом порядке. Поскольку os.setuid, os.setgid и preexec_fn поддерживаются только в Unix, это решение не переносимо для других видов операционных систем.

Следующий код представляет собой script (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()

Вы можете вызвать этот script следующим образом:

Начать с 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

Станьте non-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

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

Ответ 2

Существует метод os.setuid(). Вы можете использовать его для изменения текущего пользователя для этого script.

Одно из решений - где-то, где начинается ребенок, вызывать os.setuid() и os.setgid(), чтобы изменить идентификатор пользователя и группы, и после этого вызвать один из os.exec *, чтобы создать нового ребенка. Новорожденный ребенок будет работать с менее мощным пользователем, не имея возможности снова стать более мощным.

Другой - это делать, когда запускается демон (главный процесс), а затем все вновь созданные процессы будут запускаться под одним и тем же пользователем.

Информацию смотрите в manpage для setuid.

Ответ 3

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

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

Затем, если вам нужно прочитать из процесса stdout:

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

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

Ответ 4

Включить пользователя в sudo как не требующий пароля

username ALL=(ALL) NOPASSWD: ALL

Затем вызовите корневую функцию с помощью sudo, например:

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