94

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?

martineau
  • 119,623
  • 25
  • 170
  • 301
Paul Hoffman
  • 1,820
  • 3
  • 15
  • 20

10 Answers10

91

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!

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 3
    I am assuming Paul is worried about different systems using different uids for administrators. Normally id(root) == 0, but it is not a must and on some systems it's really not equal. – Stan May 10 '10 at 22:44
  • @Stan: then EAFP would indeed be better – msw May 10 '10 at 22:46
  • 12
    The assumption that effective UID == 0 means "root" is pretty deeply ingrained into UNIX code (including throughout most UNIX-like kernel sources). Technically under Linux that assumption is NOT necessarily correct. The Linux "capabilities" model allows the system to run with more fine-grained controls delegated via process inheritance (lcap2 wrappers) or potentially through extended filesystem attributes. Also SELinux features could play havoc with such assumptions. For 99% of the systems out there, geteuid() == 0 is sufficient; for the rest try: ... except: is your friend. – Jim Dennis May 10 '10 at 22:50
37

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.

Nuno André
  • 4,739
  • 1
  • 33
  • 46
msw
  • 42,753
  • 9
  • 87
  • 112
  • This is the most portable way and even works under a bit more exotic circumstances, like with SELinux limiting root privileges. – Florian Diesch May 10 '10 at 23:55
  • 5
    This may actually be worse depending on the situation. Hmm but Florian has a point. – L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ May 11 '10 at 00:06
  • @Longpoke - are you referring to the situation where a user may have rename permissions in /etc but - say lack the ability to remove a file from that path? If that is what you mean, then trying to determine what capabilities the user does have in advance is a fool's errand. Better to structure your code for "transactional" semantics where A and B can be rolled-back if C fails. – msw May 11 '10 at 00:20
  • 1
    @msw: I initially thought, "you should just check if you're root or not and exit", to avoid any transactional consequence, but SELinux, ACLs, and capabilities definately break that, so I guess the only way to really do it right is what you suggested. – L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ May 11 '10 at 02:05
  • Using try/except is fine for os.rename, but isn't flexible enough for the other thing I mentioned, "installing with apt-get". I fully agree that euid could fail on permissions-based systems, which is why I asked the question in the first place. However, I don't think that try/except covers all the issues either. – Paul Hoffman May 12 '10 at 15:15
  • Agreed about apt-get, see below. – msw May 12 '10 at 16:17
  • I can only mark one answer as "the correct one", but your addition below (and other riffs on it that I can think of) nail it. Yes, I should be checking for errno.EPERM on each action. – Paul Hoffman May 14 '10 at 14:24
  • I know this is old, but in the end, you don't exactly need root privileges, just enough privileges to get the job done. Running as root is simply the easiest way to escalate privileges beyond what Linux and Linux-based OSs give to administrators. –  Mar 13 '15 at 16:02
  • Not working for me running Python 2.7.3 on Linux. It throws `OSError: [Errno 2] No such file or directory`, doesn't matter if I run it with or without `sudo`. – Or B Mar 14 '15 at 15:08
  • @OrB That probably means that there is no such file or directory for whatever it is you are trying to do. Please phrase your issue in the form of a **new** question because your one sentence complaint here doesn't afford enough information to even guess what you are trying to accomplish nor why it has nothing to do with this answer. – msw Mar 14 '15 at 15:35
  • 1
    Sometimes you need to know if all the parts of the task are going to be successful before you start making changes. I don't want to do steps 1, 2, and 3 just to find out I can't do step 4 and hope I can back out what's been done. – keithpjolley Jul 02 '19 at 22:47
  • actually I upvoted for the "Easier to Ask Forgiveness than Permission" principle – aster94 Apr 11 '20 at 13:20
  • Based on "Flat is better than nested", wouldn't it be better to use `PermissionError` rather than `IOError` and checking the error code, at least in Python 3.x? – dericke May 25 '21 at 15:42
  • Am I completely mad, or couldn't this do severe damage to your system, i.e. if you turn out to be root, and renaming /etc/foo causes severe problems!? Mightn't it be better to see if you can *create* a dir in /etc: `mkdir /etc/my_extremely_silly_and_implausible_directory` (and then delete it). I appreciate that having a directory in /etc called "foo" which does something crucial would be the height of silliness and implausibility... but you never know what people get up to! – mike rodent Jan 11 '22 at 19:12
18

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

jcarballo
  • 27,395
  • 3
  • 28
  • 28
  • 1
    Just to clarify, this lets you check if the user has root privileges in a literal sense, but it will not give the instance of the program you are running root acccess if it didn't have it at launch. You could try something like [this](https://stackoverflow.com/a/3819134/3358251) for that. – Mateo de Mayo Jun 26 '20 at 23:04
14

For linux:

def is_root():
    return os.geteuid() == 0
Dmytro
  • 1,290
  • 17
  • 21
11

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)
vossman77
  • 1,397
  • 14
  • 13
7

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

Jim Dennis
  • 17,054
  • 13
  • 68
  • 116
7
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.

Thomas
  • 8,357
  • 15
  • 45
  • 81
  • 1
    This answers the question since we're looking for "root-like" privileges, but why do you need the second part of the `if` in this case? Isn't it sufficient to check for the existence of `SUDO_UID`? – mike rodent Jan 11 '22 at 19:09
  • @DanielBraun what is "ambiguous" about the docker run command you wrote? – Pedro A Apr 05 '23 at 15:08
3

My app works with this code:

import os
user = os.getenv("SUDO_USER")
if user is None:
    print "This program need 'sudo'"
    exit()
Guillaume
  • 2,752
  • 5
  • 27
  • 42
  • Bad code. The error thrown here is `TypeError`, since if you're not running as root, `os.getenv("SUDO_USER")` return `None` which is of type `NoneType`, and you cannot concatenate `str` and `NoneType`. If you want to use `os.getenv("SUDO_USER")` to solve the problem, you should do it like this: `if os.getenv("SUDO_USER") == None: print "This program need 'sudo'"; exit()` – Or B Mar 14 '15 at 15:03
  • 1
    Thanks for update, This code will only test sudo, not root access. Because it doesn't check for root, if you log in as root (or you wrote a cronjob like me) you still need to call the script with sudo, because sudo doesn't ask password on root, I was able fix my cronjob with just calling my python script with sudo. – Cediddi Mar 27 '15 at 22:56
  • A bit late to the party, but this is indeed very useful! When combined with something like `os.getenv('HOME')`, this will tell you whether the current user is a `root` or `sudo` which is what generally your script needs to know. – Prahlad Yeri Mar 01 '16 at 07:00
  • @PrahladYeri Not sure what you mean about HOME, recall that `sudo` and `sudo -E` have different outputs for `HOME`, and both are sudo. – Pedro A Apr 05 '23 at 15:11
3

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.

msw
  • 42,753
  • 9
  • 87
  • 112
1

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.

Stan
  • 2,487
  • 18
  • 27