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

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

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

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

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

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

4 ответов


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

  1. определите функцию, которая установит gid и uid запущенного процесса.
  2. передайте эту функцию в качестве параметра preexec_fn подпроцесс.К popen

подпроцесс.Popen будет использовать модель fork/exec для использования вашего preexec_fn. Это эквивалентно вызову ОС.fork (), preexec_fn () (в дочернем процессе) и ОС.exec () (в дочернем процессе) в этом порядке. Начиная с ОС.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)/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 в 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

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


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

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

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

для получения информации посмотрите на manpage для setuid.


На самом деле, пример с 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

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


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

username ALL=(ALL) NOPASSWD: ALL
вызова функция root с 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