running a command as a super user from a python script
Asked Answered
H

9

63

So I'm trying to get a process to be run as a super user from within a python script using subprocess. In the ipython shell something like

proc = subprocess.Popen('sudo apach2ctl restart',
                        shell=True, stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)

works fine, but as soon as I stick it into a script I start getting: sudo: apach2ctl: command not found.

I would guess this is due to the way sudo handles environments on ubuntu. (I've also tried sudo -E apche2ctl restart and sudo env path=$PATH apache2ctl restart with no avail)

So my question is basically, if I want to run apache2ctl restart as super user that prompts the user for the super user password when required, how should I go about doing this? I have no intention of storing passwords in the script.

Edit:

I've tried passing in the commands as both a string and tokenized into a list. In the python interpreter, with a string I'll get the password prompt properly (still doesnt work in a python script as in my original problem), a list just gives the help screen for sudo.

Edit 2:

So what I gather is that while Popen will work with some commands just as strings when shell=True, it takes

proc = subprocess.Popen(['sudo','/usr/sbin/apache2ctl','restart'])

without 'shell=True' to get sudo to work.

Thanks!

Halfmoon answered 19/2, 2009 at 22:33 Comment(3)
How about configuring sudo so that this user can run just this command without requiring a password?Elodiaelodie
You may want to use proc.wait() depending on what you're doing; wait doesn't proceed until the child process has finished running.Paroxysm
You get the error sudo: apach2ctl: command not found because you have a typo in the service name apache2ctl.Kostival
C
24

Try giving the full path to apache2ctl.

Consummation answered 19/2, 2009 at 22:46 Comment(2)
I think you've hit on it. Running a script from the interpreter inherits $PATH from the interactive shell which is running it. When run as a script, however, $PATH is inherited from a NON-interactive shell and may well not be the same. It's best to always specify full paths when using popen().Incomparable
Yeah - whenever you reference binaries from scripts, you can't rely on $PATH being there for you. By the way, "apch2ctl" is misspelled. ;)Croupier
N
34

Try:

subprocess.call(['sudo', 'apach2ctl', 'restart'])

The subprocess needs to access the real stdin/out/err for it to be able to prompt you, and read in your password. If you set them up as pipes, you need to feed the password into that pipe yourself.

If you don't define them, then it grabs sys.stdout, etc...

Natascha answered 19/2, 2009 at 23:11 Comment(1)
You didnt solve the password issue, as solved below.Phenolphthalein
C
24

Try giving the full path to apache2ctl.

Consummation answered 19/2, 2009 at 22:46 Comment(2)
I think you've hit on it. Running a script from the interpreter inherits $PATH from the interactive shell which is running it. When run as a script, however, $PATH is inherited from a NON-interactive shell and may well not be the same. It's best to always specify full paths when using popen().Incomparable
Yeah - whenever you reference binaries from scripts, you can't rely on $PATH being there for you. By the way, "apch2ctl" is misspelled. ;)Croupier
J
19

Another way is to make your user a password-less sudo user.

Type the following on command line:

sudo visudo

Then add the following and replace the <username> with yours:

<username> ALL=(ALL) NOPASSWD: ALL

This will allow the user to execute sudo command without having to ask for password (including application launched by the said user. This might be a security risk though

Journalist answered 19/2, 2009 at 22:33 Comment(0)
F
16

I used this for python 3.5. I did it using subprocess module.Using the password like this is very insecure.

The subprocess module takes command as a list of strings so either create a list beforehand using split() or pass the whole list later. Read the documentation for more information.

What we are doing here is echoing the password and then using pipe we pass it on to the sudo through '-S' argument.

#!/usr/bin/env python
import subprocess

sudo_password = 'mysecretpass'
command = 'apach2ctl restart'
command = command.split()

cmd1 = subprocess.Popen(['echo',sudo_password], stdout=subprocess.PIPE)
cmd2 = subprocess.Popen(['sudo','-S'] + command, stdin=cmd1.stdout, stdout=subprocess.PIPE)

output = cmd2.stdout.read().decode() 

Note - For obvious reasons don't store passwords directly in code rather use environment variables or a different safer method to get the same

Fructification answered 19/2, 2009 at 22:33 Comment(2)
if it were the case that I needed to input a password, I wouldn't store the password anywhere in the code but instead use something like getpass, making your first command to be cmd1 = subprocess.Popen(['echo',getpass.getpass('password:')], stdout=subprocess.PIPE) or what Jozsef Turi mentionedHalfmoon
You should never store your password/username in your code, It will lead to privilege escalation.Sladen
K
9

The safest way to do this is to prompt for the password beforehand and then pipe it into the command. Prompting for the password will avoid having the password saved anywhere in your code and it also won't show up in your bash history. Here's an example:

from getpass import getpass
from subprocess import Popen, PIPE

password = getpass("Please enter your password: ")
# sudo requires the flag '-S' in order to take input from stdin
proc = Popen("sudo -S apach2ctl restart".split(), stdin=PIPE, stdout=PIPE, stderr=PIPE)
# Popen only accepts byte-arrays so you must encode the string
proc.communicate(password.encode())
Kostival answered 19/2, 2009 at 22:33 Comment(1)
I like this one. However I'd use shlex.split() instead of str.split() for safer argument splits.Typecase
E
5

You have to use Popen like this:

cmd = ['sudo', 'apache2ctl', 'restart']
proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

It expects a list.

Elodiaelodie answered 19/2, 2009 at 22:35 Comment(3)
Read docs.python.org/library/subprocess.html There is a difference between a string and sequence args when shell=True.Microcrystalline
Ah I see, I've never used it like that before.Elodiaelodie
Don't set shell=True when you're passing a list into it.Ligule
P
4

To run a command as root, and pass it the password at the command prompt, you could do it as so:

import subprocess
from getpass import getpass

ls = "sudo -S ls -al".split()
cmd = subprocess.run(
    ls, stdout=subprocess.PIPE, input=getpass("password: "), encoding="ascii",
)
print(cmd.stdout)

For your example, probably something like this:

import subprocess
from getpass import getpass

restart_apache = "sudo /usr/sbin/apache2ctl restart".split()
proc = subprocess.run(
    restart_apache,
    stdout=subprocess.PIPE,
    input=getpass("password: "),
    encoding="ascii",
)
Pliant answered 19/2, 2009 at 22:33 Comment(0)
C
4

I tried all the solutions, but did not work. Wanted to run long running tasks with Celery but for these I needed to run sudo chown command with subprocess.call().

This is what worked for me:

To add safe environment variables, in command line, type:

export MY_SUDO_PASS="user_password_here"

To test if it's working type:

echo $MY_SUDO_PASS
 > user_password_here

To run it at system startup add it to the end of this file:

nano ~/.bashrc  

#.bashrc
...
existing_content:

  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi
...

export MY_SUDO_PASS="user_password_here"

You can add all your environment variables passwords, usernames, host, etc here later.

If your variables are ready you can run:

To update:

echo $MY_SUDO_PASS | sudo -S apt-get update

Or to install Midnight Commander

echo $MY_SUDO_PASS | sudo -S apt-get install mc

To start Midnight Commander with sudo

echo $MY_SUDO_PASS | sudo -S mc

Or from python shell (or Django/Celery), to change directory ownership recursively:

python
>> import subprocess
>> subprocess.call('echo $MY_SUDO_PASS | sudo -S chown -R username_here /home/username_here/folder_to_change_ownership_recursivley', shell=True)

Hope it helps.

Corral answered 19/2, 2009 at 22:33 Comment(1)
It should go without saying but storing your sudo password in an unencrypted file and adding it your shell environment is not at all secureWaziristan
E
0

You can use this way and even catch errors, even can add variables to your commands. -

val = 'xy
response = Popen(f"(sudo {val})", stderr=PIPE, stdout=PIPE, shell=True)
output, errors = response.communicate()

Hope this helps.

Escamilla answered 19/2, 2009 at 22:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.