Запуск дочерних процессов от имени другого пользователя из долгосрочного Python-процесса
У меня есть долгосрочный демонизированный процесс на Python, который использует subprocess
для создания новых дочерних процессов при возникновении определенных событий. Долгосрочный процесс запускается пользователем с привилегиями суперпользователя, и мне нужно, чтобы дочерние процессы выполнялись от имени другого пользователя (например, "nobody"), при этом родительский процесс сохранял привилегии суперпользователя.
В данный момент я использую следующую команду:
su -m nobody -c <программа для выполнения как дочерний процесс>
Однако это выглядит громоздко и завершает работу не очень аккуратно.
Есть ли способ сделать это программно, без использования su
? Я рассматриваю методы os.set*uid
, но документация в стандартной библиотеке Python по этому вопросу довольно скудная.
2 ответ(ов)
Судя по тому, что вы упомянули демона, можно заключить, что вы работаете на Unix-подобной операционной системе. Это важно, потому что способ выполнения этой задачи зависит от типа операционной системы. Этот ответ применим только к Unix, включая Linux и Mac OS X.
- Определите функцию, которая будет устанавливать gid и uid текущего процесса.
- Передайте эту функцию в качестве параметра
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
Примечание: ожидание завершения дочернего процесса родительским процессом используется только в демонстрационных целях. Я сделал это, чтобы родитель и ребенок могли делить терминал. Демон же обычно не имеет терминала и редко ждет завершения дочернего процесса.
Конечно, вот перевод на русский в стиле ответа на 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
Надеюсь, это будет полезно не только в моем случае.
Запуск команд оболочки и захват вывода
Как изменить порядок столбцов в DataFrame?
'pip' не распознан как командa внутреннего или внешнего формата
Почему statistics.mean() работает так медленно?
Есть ли разница между поднятием экземпляра класса Exception и самого класса Exception?