Запуск дочерних процессов от имени пользователя, отличного от длительного процесса
У меня есть длительный, демонизированный процесс 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.
- определите функцию, которая установит gid и uid запущенного процесса.
- передайте эту функцию в качестве параметра 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