58

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!

Silfheed
  • 11,585
  • 11
  • 55
  • 67
  • 4
    How about configuring sudo so that this user can run just this command without requiring a password? – Harley Holcombe Feb 19 '09 at 23:13
  • You may want to use [`proc.wait()`](https://docs.python.org/2/library/subprocess.html#subprocess.Popen.wait) depending on what you're doing; `wait` doesn't proceed until the child process has finished running. – craymichael Nov 08 '16 at 19:58
  • You get the error `sudo: apach2ctl: command not found` because you have a typo in the service name `apache2ctl`. – Josh Correia Aug 10 '22 at 21:30

9 Answers9

31

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...

Mike Boers
  • 6,665
  • 3
  • 31
  • 40
22

Try giving the full path to apache2ctl.

dwc
  • 24,196
  • 7
  • 44
  • 55
  • 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(). – Ben Blank Feb 19 '09 at 22:52
  • Yeah - whenever you reference binaries from scripts, you can't rely on $PATH being there for you. By the way, "apch2ctl" is misspelled. ;) – ojrac Feb 19 '09 at 22:53
18

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

Michael Loo
  • 598
  • 5
  • 11
14

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

Nandesh
  • 4,437
  • 2
  • 20
  • 26
  • 1
    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 mentioned – Silfheed Apr 05 '18 at 17:47
  • You should never store your password/username in your code, It will lead to privilege escalation. – boaz tene Apr 15 '23 at 16:34
8

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())
Josh Correia
  • 3,807
  • 3
  • 33
  • 50
  • 1
    I like this one. However I'd use `shlex.split()` instead of `str.split()` for safer argument splits. – Szabolcs Nov 29 '19 at 11:48
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.

Harley Holcombe
  • 175,848
  • 15
  • 70
  • 63
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",
)
clamytoe
  • 41
  • 4
3

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.

jturi
  • 1,615
  • 15
  • 11
  • 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 secure – Brandon Jul 29 '22 at 23:10
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.

ashraf minhaj
  • 504
  • 2
  • 14