3

I am trying to execute a command in python 3.6 as a different user with popen from subprocess but it will still execute as the user who called the script (i plan to call it as root). I am using threads and therefore it is important that i don't violate the user rights when 2 threads execute in parallel.

proc = subprocess.Popen(['echo $USER; touch myFile.txt'],
                          shell=True,
                          env={'FOO':'bar', 'USER':'www-data'},
                          stdout=subprocess.PIPE)

The example above will still create the myFile.txt with my user_id 1000

I tried different approaches :

  1. tried with as described in Run child processes as different user from a long running Python process by copying the os.environment and changed the user, etc
    (note this is for python 2)

  2. tried with as described in https://docs.python.org/3.6/library/subprocess.html#popen-constructor by using start_new_session=True

My Last option is to prefix the command with sudo -u username command but i don't think this is the elegant way.

Chris
  • 315
  • 1
  • 4
  • 17
  • 1
    Does this answer your question? [Run child processes as different user from a long running Python process](https://stackoverflow.com/questions/1770209/run-child-processes-as-different-user-from-a-long-running-python-process) – lemonhead Apr 19 '20 at 09:54
  • `sudo -u username command` is indeed an elegant way! – Kaif Khan Apr 19 '20 at 09:57
  • 1
    @lemonhead i mentioned it under 1. – Chris Apr 19 '20 at 09:58
  • @Chris is there a reason that approach doesn't do what you are aiming for? I think the piece you are missing in your example is doing the demotion via passing through a func to `preexec_fn` – lemonhead Apr 19 '20 at 10:18
  • @lemonhead as i mentioned under 1.: i tried this approach and it didn't work either. I called: `subprocess.Popen(['echo $USER; touch myFile.txt'], shell=True, env={'FOO':'bar', 'USER':'www-data'}, stdout=subprocess.PIPE, preexec_fn=demote(33,33))` and demote returned with an exception
    `demote(33,33)`
    `.result at 0x7f7f20cd1ea0>`
    – Chris Apr 19 '20 at 10:33
  • @Chris hard to tell what the exact issue is without a full reproducible code snippet / the exception trace, but just using `preexec_fn` appropriately should do the trick I believe -- see my answer below – lemonhead Apr 19 '20 at 10:58
  • btw are you sure 33 is both the uid and gid for your target user? I don't think they can both match so perhaps that's the issue you were hitting? Procedurally extracting these from username as in answer below should solve that though – lemonhead Apr 19 '20 at 11:01
  • ```cat /etc/passwd | grep www-data --> www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin``` – Chris Apr 19 '20 at 11:39

1 Answers1

5

The standard way [POSIX only] would be to use preexec_fn to set gid and uid as described in more detail in this answer

Something like this should do the trick -- for completeness I've also modified your initial snippet to include the other environment variables you'd likely want to set, but just setting preexec_fn should be sufficient for the simple command you are running:

import os, pwd, subprocess

def demote(user_uid, user_gid):
    def result():
        os.setgid(user_gid)
        os.setuid(user_uid)
    return result

def exec_cmd(username):
    # get user info from username
    pw_record = pwd.getpwnam(username)
    homedir = pw_record.pw_dir
    user_uid = pw_record.pw_uid
    user_gid = pw_record.pw_gid
    env = os.environ.copy()
    env.update({'HOME': homedir, 'LOGNAME': username, 'PWD': os.getcwd(), 'FOO': 'bar', 'USER': username})

    # execute the command
    proc = subprocess.Popen(['echo $USER; touch myFile.txt'],
                              shell=True,
                              env=env,
                              preexec_fn=demote(user_uid, user_gid),
                              stdout=subprocess.PIPE)
    proc.wait()


exec_cmd('www-data')

Note that you'll also need to be sure the current working directory is accessible (e.g. for writing) by the demoted user since not overriding it explicitly

lemonhead
  • 5,328
  • 1
  • 13
  • 25
  • alright, this works ... even in threads. I added a individual sleep time and it worked. I didn't know what i previously did wrong but thank you. You are using the `preexec_fn` argument but under 2. in my question they [docs](https://docs.python.org/3.6/library/subprocess.html#popen-constructor) are saying you have to use `start_new_session=True` instead when using threads. Can you or someone explain it to me? – Chris Apr 19 '20 at 11:36
  • pylint print a warning: W1509: Using preexec_fn keyword which may be unsafe in the presence of threads (subprocess-popen-pr eexec-fn) – Chris Apr 19 '20 at 11:43
  • Is therre a similar way to run a subprocess in windows with a suer which is not piveleged ? i.e. my python script is running as admin and i want to run the subprocess as a non priveleged user – Bored002 Jun 16 '20 at 05:54