Uruchom procesy potomne jako inny użytkownik niż długo działający proces

Mam długo działający, demonizowany proces Pythona, który używa podprocesu do wywoływania nowych procesów potomnych, gdy wystąpią pewne zdarzenia. Długotrwały proces jest uruchamiany przez użytkownika z uprawnieniami super user. Proces potomny musi być uruchomiony jako inny użytkownik (np. "nikt"), zachowując uprawnienia super użytkownika dla procesu macierzystego.

Obecnie używam

su -m nobody -c <program to execute as a child>
Ale to wydaje się ciężkie i nie umiera zbyt czysto.

Czy jest sposób na osiągnąć to programowo zamiast używać su? Patrzę na system operacyjny.set * UID methods, ale doc w Python std lib jest dość rzadki w tej dziedzinie.

Author: Peter Parente, 2009-11-20

4 answers

Skoro wspomniałeś o demonie, mogę wywnioskować, że działasz na Uniksopodobnym systemie operacyjnym. Ma to znaczenie, ponieważ jak to zrobić zależy od rodzaju systemu operacyjnego. Ta odpowiedź dotyczy tylko do Unix , W tym Linux i Mac OS X.

  1. Zdefiniuj funkcję, która ustawi gid i uid uruchomionego procesu.
  2. przekaż tę funkcję jako parametr preexec_fn do podprocesu.Popen

Podproces.Popen użyje forka / exec model do użycia preexec_fn. Jest to równoznaczne z wywołaniem systemu operacyjnego.fork (), preexec_fn () (w procesie potomnym) i os.exec () (w procesie potomnym) w tej kolejności. Od os.setuid, os.setgid i preexec_fn są obsługiwane tylko w systemie Unix, rozwiązanie to nie jest przenośne dla innych systemów operacyjnych.

Poniższy kod jest skryptem (Python 2.4+), który pokazuje, jak to zrobić:

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()

Możesz wywołać ten skrypt w następujący sposób:

Zacznij jako 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

Staje się non-root w procesie potomnym...

(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

Gdy proces potomny zakończy się, wracamy do roota w rodzicu ...

(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

Zauważ , że proces macierzysty czeka na zakończenie procesu potomnego tylko do celów demonstracyjnych . Zrobiłem to, aby rodzic i dziecko mogli dzielić terminal. Demon nie miałby terminala i rzadko czekałby na zakończenie procesu potomnego.

 66
Author: Walker Hale IV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-17 21:41:18

Istnieje os.setuid() metoda. Możesz go użyć do zmiany bieżącego użytkownika dla tego skryptu.

Jednym z rozwiązań jest, gdzieś, gdzie dziecko zaczyna, wywołanie os.setuid() i os.setgid(), aby zmienić identyfikator użytkownika i grupy, a następnie wywołanie jednego z systemu operacyjnego.exec * Metody rodzenia nowego dziecka. Nowo narodzone dziecko będzie działać z mniej potężnym Użytkownikiem bez możliwości ponownego stawania się potężniejszym.

Innym jest to, aby to zrobić, gdy daemon (proces główny) rozpocznie się i wtedy wszystkie nowo powstałe procesy będą uruchamiane przez tego samego użytkownika.

Aby uzyskać informacje, zajrzyj na stronę podręcznika setuid .

 11
Author: Emil Ivanov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-21 17:37:07

Właściwie, przykład z preexec_fn nie zadziałał dla mnie.
Moje rozwiązanie, które działa poprawnie, aby uruchomić jakieś polecenie powłoki od innego użytkownika i uzyskać jego wyjście to:

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

Następnie, jeśli chcesz odczytać z procesu stdout:

cond=True
while (cond):
  line=apipe.stdout.getline()
  if (....):
    cond=False
Nadzieja, przydaje się nie tylko w moim przypadku.
 1
Author: ETech,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-17 21:16:23

Włącz użytkownika w sudo jako nie wymagającego hasła

username ALL=(ALL) NOPASSWD: ALL

Następnie wywołaj funkcję root za pomocą sudo, np.:

import pexpect
child = pexpect.spawn('sudo apachectl restart')
for i in child: print i #if you want to see the output from the process
 -4
Author: user2099484,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-14 08:03:55