web-dev-qa-db-fra.com

Exécuter des processus enfants en tant qu'utilisateur différent d'un long processus Python

J'ai un long processus démonifié Python qui utilise un sous-processus pour générer de nouveaux processus enfants lorsque certains événements se produisent. Le processus long est démarré par un utilisateur disposant de privilèges de super-utilisateur. J'ai besoin du les processus enfants apparaissent pour s'exécuter en tant qu'utilisateur différent (par exemple, "personne") tout en conservant les privilèges de superutilisateur pour le processus parent.

J'utilise actuellement

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

mais cela semble lourd et ne meurt pas très proprement.

Existe-t-il un moyen d'accomplir cela par programme au lieu d'utiliser su? Je regarde les méthodes os.set * uid, mais la doc dans la bibliothèque Python std lib est assez rare dans ce domaine.

42
Peter Parente

Puisque vous avez mentionné un démon, je peux conclure que vous utilisez un système d'exploitation de type Unix. Cela est important, car la façon de procéder dépend du type de système d'exploitation. Cette réponse s'applique uniquement à nix, y compris Linux et Mac OS X.

  1. Définissez une fonction qui définira le gid et l'uid du processus en cours.
  2. Passez cette fonction en tant que paramètre preexec_fn au sous-processus.

subprocess.Popen utilisera le modèle fork/exec pour utiliser votre preexec_fn. Cela équivaut à appeler os.fork (), preexec_fn () (dans le processus enfant) et os.exec () (dans le processus enfant) dans cet ordre. Étant donné que os.setuid, os.setgid et preexec_fn ne sont tous pris en charge que sur Unix, cette solution n'est pas portable sur d'autres types de systèmes d'exploitation.

Le code suivant est un script (Python 2.4+) qui montre comment procéder:

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 __== '__main__':
    main()

Vous pouvez invoquer ce script comme ceci:

Commencez en tant que 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

Devenez non root dans un processus enfant ...

(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

Lorsque le processus enfant se termine, nous retournons à la racine dans 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

Remarque que le processus parent doit attendre que le processus enfant se termine uniquement à des fins de démonstration . Je l'ai fait pour que le parent et l'enfant puissent partager un terminal. Un démon n'aurait pas de terminal et attendrait rarement qu'un processus enfant se termine.

79
Walker Hale IV

Il existe une méthode os.setuid() . Vous pouvez l'utiliser pour changer l'utilisateur actuel de ce script.

Une solution consiste, quelque part où l'enfant commence, à appeler os.setuid() et os.setgid() pour changer l'ID utilisateur et de groupe et après cet appel, l'un des os.exec * méthodes pour engendrer un nouvel enfant. L'enfant nouvellement engendré fonctionnera avec l'utilisateur le moins puissant sans avoir la possibilité de redevenir plus puissant.

Une autre consiste à le faire lorsque le démon (le processus maître) démarre et que tous les processus nouvellement créés auront été exécutés sous le même utilisateur.

Pour plus d'informations, consultez la page de manuel de setuid .

11
Emil Ivanov

En fait, l'exemple avec preexec_fn n'a pas fonctionné pour moi.
Ma solution qui fonctionne bien pour exécuter une commande Shell d'un autre utilisateur et obtenir sa sortie est:

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

Ensuite, si vous avez besoin de lire à partir de la sortie standard du processus:

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

J'espère, c'est utile non seulement dans mon cas.

1
ETech