What is the best way for checking if the user of a script has root-like privileges?
Question:
I have a Python script that will be doing a lot of things that would require root-level privileges, such as moving files in /etc, installing with apt-get, and so on. I currently have:
if os.geteuid() != 0:
exit("You need to have root privileges to run this script.nPlease try again, this time using 'sudo'. Exiting.")
Is this the best way to do the check? Are there other best practices?
Answers:
os.geteuid
gets the effective user id, which is exactly what you want, so I can’t think of any better way to perform such a check. The one bit that’s uncertain is that “root-like’ in the title: your code checks for exactly root
, no “like” about it, and indeed I wouldn’t know what “root-like but not root” would mean — so, if you mean something different than “exactly root”, perhaps you can clarify, thanks!
Under the EAFP (Easier to Ask Forgiveness than Permission) principle:
import errno
try:
os.rename('/etc/foo', '/etc/bar')
except IOError as e:
if e[0] == errno.EPERM:
sys.exit("You need root permissions to do this, laterz!")
If you are concerned about the non-portability of os.geteuid()
you probably shouldn’t be mucking with /etc
anyway.
It all depends how portable you want you app to be. If you mean bussiness, the we have to assume that administrator account does not always equal 0. This means that checking for euid 0 is not enough. Problem is, there are situations where one command will behave as if you are root and next will fail with permission denied (think SELinux & co.). Therefore it’s really better to fail gracefully and check for EPERM errno whenever it’s appropriate.
If you really want your code to be robust across a wide variety of Linux configurations I’d suggest that you consider the corner cases where someone may be using SELinux, or filesystem ACLs, or the “capabilities” features that have been in the Linux kernel since v. 2.2 or so. Your process might be running under some wrapper that has used SELinux, or some Linux capabilities library, such as libcap2 libcap-ng, or fscaps or elfcap via something more exotic like Niels Provos’ wonderful and sadly under-appreciated systrace system.
All of these are ways that you code might be running as non-root and yet your process might have been delegated the necessary access to perform its work without EUID==0.
So I’d suggest that you consider writing your code more Pythonically, by wrapping operations that may fail due to permissions or other issues with exception handling code. If you’re shelling out to perform various operations (e.g. using the subprocess
module) you might offer to prefix all such calls with sudo
(as a command line, environment, or .rc file option, for example). If it’s being run interactively you can offer to re-execute any commands that raise permissions related exceptions using sudo
(optionally only if you find sudo
on the os.environ[‘PATH’]).
Overall it’s true that most Linux and UNIX systems still have most of their administration done by a ‘root’ privileged user. However, it’s old school and we, as programmers, should try to support newer models. Trying your operations and letting the exception handling do its job allows your code to work under any system that transparently permits the operations you need, and being aware of and ready to use sudo
is a nice touch (as it is, by far, the most widespread tool for controlled delegation of system privileges).
Answer to the second part of the question
(sorry the comment box was too small)
Paul Hoffman, you are correct, I only addressed one part of your question dealing with intrinsics, but it wouldn’t be a worthy scripting language if it couldn’t handle apt-get
. The preferred library is a tad verbose but it does the job:
>>> apt_get = ['/usr/bin/apt-get', 'install', 'python']
>>> p = subprocess.Popen(apt_get, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> p.wait()
100 # Houston, we have a problem.
>>> p.stderr.read()
'E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)'
'E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?n'
But Popen
is a generalized tool and can be wrapped for convenience:
$ cat apt.py
import errno
import subprocess
def get_install(package):
cmd = '/usr/bin/apt-get install'.split()
cmd.append(package)
output_kw = {'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE}
p = subprocess.Popen(cmd, **output_kw)
status = p.wait()
error = p.stderr.read().lower()
if status and 'permission denied' in error:
raise OSError(errno.EACCES, 'Permission denied running apt-get')
# other conditions here as you require
$ python
>>> import apt
>>> apt.get_install('python')
Traceback ...
OSError: [Errno 13] Permission denied running apt-get
And now we’re back to exception handling. I will decline to comment on the Java-like over-generality of the subprocess module.
I like to check for sudo in the environmental variables:
if not 'SUDO_UID' in os.environ.keys():
print "this program requires super user priv."
sys.exit(1)
You can prompt the user for sudo access:
import os, subprocess
def prompt_sudo():
ret = 0
if os.geteuid() != 0:
msg = "[sudo] password for %u:"
ret = subprocess.check_call("sudo -v -p '%s'" % msg, shell=True)
return ret
if prompt_sudo() != 0:
# the user wasn't authenticated as a sudoer, exit?
The sudo -v
switch update the user’s cached credentials (see man sudo
).
My app works with this code:
import os
user = os.getenv("SUDO_USER")
if user is None:
print "This program need 'sudo'"
exit()
For linux:
def is_root():
return os.geteuid() == 0
import os
def check_privileges():
if not os.environ.get("SUDO_UID") and os.geteuid() != 0:
raise PermissionError("You need to run this script with sudo or as root.")
SUDO_UID
is not available if script is not run with sudo
.
I have a Python script that will be doing a lot of things that would require root-level privileges, such as moving files in /etc, installing with apt-get, and so on. I currently have:
if os.geteuid() != 0:
exit("You need to have root privileges to run this script.nPlease try again, this time using 'sudo'. Exiting.")
Is this the best way to do the check? Are there other best practices?
os.geteuid
gets the effective user id, which is exactly what you want, so I can’t think of any better way to perform such a check. The one bit that’s uncertain is that “root-like’ in the title: your code checks for exactly root
, no “like” about it, and indeed I wouldn’t know what “root-like but not root” would mean — so, if you mean something different than “exactly root”, perhaps you can clarify, thanks!
Under the EAFP (Easier to Ask Forgiveness than Permission) principle:
import errno
try:
os.rename('/etc/foo', '/etc/bar')
except IOError as e:
if e[0] == errno.EPERM:
sys.exit("You need root permissions to do this, laterz!")
If you are concerned about the non-portability of os.geteuid()
you probably shouldn’t be mucking with /etc
anyway.
It all depends how portable you want you app to be. If you mean bussiness, the we have to assume that administrator account does not always equal 0. This means that checking for euid 0 is not enough. Problem is, there are situations where one command will behave as if you are root and next will fail with permission denied (think SELinux & co.). Therefore it’s really better to fail gracefully and check for EPERM errno whenever it’s appropriate.
If you really want your code to be robust across a wide variety of Linux configurations I’d suggest that you consider the corner cases where someone may be using SELinux, or filesystem ACLs, or the “capabilities” features that have been in the Linux kernel since v. 2.2 or so. Your process might be running under some wrapper that has used SELinux, or some Linux capabilities library, such as libcap2 libcap-ng, or fscaps or elfcap via something more exotic like Niels Provos’ wonderful and sadly under-appreciated systrace system.
All of these are ways that you code might be running as non-root and yet your process might have been delegated the necessary access to perform its work without EUID==0.
So I’d suggest that you consider writing your code more Pythonically, by wrapping operations that may fail due to permissions or other issues with exception handling code. If you’re shelling out to perform various operations (e.g. using the subprocess
module) you might offer to prefix all such calls with sudo
(as a command line, environment, or .rc file option, for example). If it’s being run interactively you can offer to re-execute any commands that raise permissions related exceptions using sudo
(optionally only if you find sudo
on the os.environ[‘PATH’]).
Overall it’s true that most Linux and UNIX systems still have most of their administration done by a ‘root’ privileged user. However, it’s old school and we, as programmers, should try to support newer models. Trying your operations and letting the exception handling do its job allows your code to work under any system that transparently permits the operations you need, and being aware of and ready to use sudo
is a nice touch (as it is, by far, the most widespread tool for controlled delegation of system privileges).
Answer to the second part of the question
(sorry the comment box was too small)
Paul Hoffman, you are correct, I only addressed one part of your question dealing with intrinsics, but it wouldn’t be a worthy scripting language if it couldn’t handle apt-get
. The preferred library is a tad verbose but it does the job:
>>> apt_get = ['/usr/bin/apt-get', 'install', 'python']
>>> p = subprocess.Popen(apt_get, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> p.wait()
100 # Houston, we have a problem.
>>> p.stderr.read()
'E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)'
'E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?n'
But Popen
is a generalized tool and can be wrapped for convenience:
$ cat apt.py
import errno
import subprocess
def get_install(package):
cmd = '/usr/bin/apt-get install'.split()
cmd.append(package)
output_kw = {'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE}
p = subprocess.Popen(cmd, **output_kw)
status = p.wait()
error = p.stderr.read().lower()
if status and 'permission denied' in error:
raise OSError(errno.EACCES, 'Permission denied running apt-get')
# other conditions here as you require
$ python
>>> import apt
>>> apt.get_install('python')
Traceback ...
OSError: [Errno 13] Permission denied running apt-get
And now we’re back to exception handling. I will decline to comment on the Java-like over-generality of the subprocess module.
I like to check for sudo in the environmental variables:
if not 'SUDO_UID' in os.environ.keys(): print "this program requires super user priv." sys.exit(1)
You can prompt the user for sudo access:
import os, subprocess
def prompt_sudo():
ret = 0
if os.geteuid() != 0:
msg = "[sudo] password for %u:"
ret = subprocess.check_call("sudo -v -p '%s'" % msg, shell=True)
return ret
if prompt_sudo() != 0:
# the user wasn't authenticated as a sudoer, exit?
The sudo -v
switch update the user’s cached credentials (see man sudo
).
My app works with this code:
import os
user = os.getenv("SUDO_USER")
if user is None:
print "This program need 'sudo'"
exit()
For linux:
def is_root():
return os.geteuid() == 0
import os
def check_privileges():
if not os.environ.get("SUDO_UID") and os.geteuid() != 0:
raise PermissionError("You need to run this script with sudo or as root.")
SUDO_UID
is not available if script is not run with sudo
.