Ejecutar procesos secundarios como un usuario diferente de un proceso de Python de larga ejecución

Tengo un proceso de Python demonizado y de larga ejecución que utiliza un subproceso para generar nuevos procesos secundarios cuando ocurren ciertos eventos. El proceso de larga ejecución es iniciado por un usuario con privilegios de superusuario. Necesito que los procesos secundarios que genera se ejecuten como un usuario diferente (p. Ej., “Nadie”) al tiempo que conserva los privilegios de superusuario para el proceso principal.

Actualmente estoy usando

su -m nobody -c  

Pero esto parece pesado y no muere muy limpiamente.

¿Hay una manera de lograr esto programáticamente en lugar de usar su? Estoy viendo los métodos os.set * uid, pero el documento en la versión estándar de Python es bastante escaso en esa área.

    Como mencionó un demonio, puedo concluir que se está ejecutando en un sistema operativo similar a Unix. Esto importa, porque la forma de hacerlo depende del tipo de sistema operativo. Esta respuesta se aplica solo a Unix , incluyendo Linux y Mac OS X.

    1. Defina una función que establezca el gid y el uid del proceso en ejecución.
    2. Pase esta función como el parámetro preexec_fn a subprocess.Popen

    subprocess.Popen usará el modelo fork / exec para usar su preexec_fn. Eso es equivalente a llamar a os.fork (), preexec_fn () (en el proceso secundario) y os.exec () (en el proceso secundario) en ese orden. Como os.setuid, os.setgid y preexec_fn solo son compatibles con Unix, esta solución no es portátil a otros tipos de sistemas operativos.

    El siguiente código es un script (Python 2.4+) que muestra cómo hacer esto:

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

    Puedes invocar este script así:

    Empezar como 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 

    Hazte no root en un proceso hijo …

     (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 

    Cuando sale el proceso hijo, volvemos a la raíz en el padre …

     (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 

    Tenga en cuenta que hacer que el proceso principal espere a que finalice el proceso secundario es solo para fines de demostración . Hice esto para que el padre y el niño pudieran compartir una terminal. Un daemon no tendría terminal y rara vez esperaría a que salga un proceso secundario.

    Hay un método os.setuid() . Puede usarlo para cambiar el usuario actual de este script.

    Una solución es, en algún lugar donde se inicie el hijo, llamar a os.setuid() y os.setgid() para cambiar el usuario y la identificación del grupo y, después, llamar a uno de los métodos os.exec * para generar un nuevo hijo. El niño recién engendrado se ejecutará con el usuario menos poderoso sin la capacidad de volverse uno más poderoso nuevamente.

    Otra es hacerlo cuando se inicie el demonio (el proceso maestro) y luego todos los procesos recién creados se ejecutarán bajo el mismo usuario.

    Para obtener más información, consulte la página de manual de setuid .

    En realidad, el ejemplo con preexec_fn no funcionó para mí.
    Mi solución que funciona bien para ejecutar algún comando de shell de otro usuario y obtener su salida es:

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

    Luego, si necesita leer del proceso stdout:

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

    Esperanza, es útil no solo en mi caso.

    Habilitar al usuario en sudo como que no requiere contraseña

     username ALL=(ALL) NOPASSWD: ALL 

    Luego llame a la función raíz con sudo , por ejemplo:

     import pexpect child = pexpect.spawn('sudo apachectl restart') for i in child: print i #if you want to see the output from the process