如何检查脚本用户是否具有类似root的权限?

96

我有一个Python脚本,需要以root权限进行很多操作,比如在/etc中移动文件、使用apt-get安装等。我目前拥有的是:

if os.geteuid() != 0:
    exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")

这是做检查的最佳方式吗?还有其他最佳实践方法吗?

10个回答

93

os.geteuid可以获取有效用户ID,这正是您想要的,因此我无法想到更好的方法来执行此类检查。值得注意的是标题中的 "root-like" 一词还有一点不确定:您的代码仅检查 root,没有任何 "like" 的内容,实际上我也不知道 "类似于 root 但不是 root" 是什么意思——因此,如果您的意思与 "exactly root" 不同,或许可以澄清一下,谢谢!


3
我假设保罗担心不同的系统会对管理员使用不同的uid。通常情况下,id(root) == 0,但并非必须如此,在某些系统上确实不相等。 - Stan
@Stan:那么采用EAFP确实更好。 - msw
12
在UNIX代码中,假设有效UID == 0表示“root”这一概念已经根深蒂固(包括大部分类UNIX内核源代码)。但在Linux技术上,这一假设不一定正确。Linux的“capabilities”模型允许系统通过进程继承(lcap2包装器)或潜在的扩展文件系统属性进行更精细的控制委派。此外,SELinux功能可能会对此类假设造成混乱。对于99%的系统,geteuid() == 0就足够了;对于其余情况,请使用try: ... except:方法。 - Jim Dennis

38

根据EAFP(宁愿请求原谅,而不是事先获得许可)原则:

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!")
如果你担心 os.geteuid() 的不可移植性,那么你可能本来就不应该乱动 /etc

5
根据情况,这可能会更糟。嗯,但是弗洛里安的观点也有道理。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
1
@msw:我最初想到的是“你应该只检查一下是否为root并退出”,以避免任何事务后果,但SELinux、ACL和能力明确破坏了这一点,所以我想唯一真正正确的方法就是您所建议的。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
我知道这已经过时了,但最终,你并不需要 root 权限,只需要足够的权限来完成工作。以 root 身份运行只是将权限提升到超出 Linux 和基于 Linux 的操作系统授予管理员的权限的最简单方法。 - user749127
在我的Linux上运行Python 2.7.3时出现问题,无论是使用sudo还是不使用sudo都会抛出“OSError:[Errno 2] No such file or directory”的错误。 - Or B
1
有时候在开始做出改变之前,你需要知道任务的所有部分是否都会成功。我不想去完成步骤1、2和3,只是为了发现我无法完成第4步,并希望能够撤销已经完成的工作。 - keithpjolley
显示剩余9条评论

18
您可以提示用户获取sudo权限:
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?
sudo -v 开关会更新用户缓存的凭证(详见 man sudo)。

1
仅作澄清,这让你可以检查用户是否以字面意义上的超级用户权限运行,但如果程序在启动时没有root权限,则它不会给予程序实例root访问权限。你可以尝试像这样去做。 - Mateo de Mayo

14

对于Linux操作系统:

def is_root():
    return os.geteuid() == 0

11

我喜欢在环境变量中检查sudo:

如果os.environ.keys()中没有'SUDO_UID',则:
  打印"该程序需要超级用户权限。"
  sys.exit(1)

1
这很简单而且整洁。谢谢! - blueren
不错。os.environ.keys()中还有我想要的其他信息! - Brōtsyorfuzthrāx
这对于“root”无效。 - buhtz
不需要使用.keys(),只需使用if not 'SUDO_UID' in os.environ:即可。--谢谢! - kol

7

如果你真的想让你的代码在各种Linux配置中变得健壮,我建议你考虑一些边缘情况,例如有人可能正在使用SELinux、文件系统ACL或者自Linux v.2.2以来就存在的“capabilities”功能。你的进程可能正在运行在某个包装器下,该包装器已经使用了SELinux或者像libcap2 libcap-ngfscapselfcap这样的Linux能力库,或者通过一些更奇特的东西(如Niels Provos的精彩而不幸未被充分重视的systrace系统)来使用。
所有这些方法都是你的代码可能以非root身份运行,但是你的进程可能已被授予执行其工作所需的访问权限,而不需要EUID==0。因此,我建议您考虑更加Pythonic地编写代码,通过使用异常处理代码包装可能由于权限或其他问题而失败的操作。如果您正在执行各种操作(例如使用subprocess模块),您可以提供在所有这样的调用之前加上sudo的选项(例如作为命令行,环境或.rc文件选项)。如果它正在交互式运行,则可以使用sudo重新执行引发权限相关异常的任何命令(可选地仅在os.environ ['PATH']上找到sudo时这样做)。总体而言,大多数Linux和UNIX系统仍然由“root”特权用户完成大部分管理工作。但是,这是老派的,我们作为程序员应该尝试支持新模型。尝试您的操作并让异常处理完成其工作,使您的代码能够在任何透明允许您需要的操作的系统下工作,并且意识到并准备使用sudo是一个不错的选择(因为它是远远最广泛的系统权限受控委派工具)。

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 在非使用 sudo 运行脚本时不可用。


1
这个回答解决了问题,因为我们正在寻找“类似于root”的特权,但是在这种情况下,为什么需要if的第二部分呢?检查SUDO_UID的存在是否足够? - mike rodent
@DanielBraun,你写的docker run命令有什么“模棱两可”的地方? - Pedro A

3

回答问题的第二部分

(很抱歉评论框太小了)

Paul Hoffman,你是正确的,我只回答了涉及内在特性的一个问题部分,但如果它不能处理 apt-get,那么它就不是一个值得信赖的脚本语言。首选的库有点冗长,但它可以完成工作:

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

但是Popen是一种通用工具,可以为了方便而进行包装:

$ 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

现在我们回到异常处理。我不想评论子进程模块类似于Java中的过度泛化。


3
把这个编辑到你的另一个答案里会更合理吧? - jpmc26

3

我的应用程序使用以下代码:

import os
user = os.getenv("SUDO_USER")
if user is None:
    print "This program need 'sudo'"
    exit()

糟糕的代码。这里抛出的错误是 TypeError,因为如果您不以 root 身份运行,则 os.getenv("SUDO_USER") 返回 None,它是 NoneType 类型,您不能将 strNoneType 连接起来。如果您想使用 os.getenv("SUDO_USER") 来解决问题,应该像这样做: if os.getenv("SUDO_USER") == None: print "This program need 'sudo'"; exit() - Or B
1
感谢更新,这段代码只测试sudo权限,而不是root权限。因为它没有检查root权限,如果您以root身份登录(或者像我一样编写了cronjob),仍然需要使用sudo调用脚本,因为sudo在root上不会要求密码。我能够通过仅使用sudo调用我的Python脚本来修复我的cronjob。 - Cediddi
有点晚了,但这确实非常有用!当与类似于os.getenv('HOME')的东西结合使用时,它将告诉您当前用户是root还是sudo,这通常是您的脚本需要知道的。 - Prahlad Yeri
@PrahladYeri 不确定你所说的“HOME”是什么意思,记住 sudosudo -E 对于 HOME 有不同的输出,而且两者都是 sudo。 - Pedro A

1

这完全取决于您希望应用程序有多可移植。如果您是指业务,那么我们必须假设管理员帐户并不总是等于0。这意味着仅检查euid 0是不够的。问题在于,有些情况下,一个命令会表现得像您是root,而下一个命令将因权限被拒绝而失败(考虑SELinux和其他情况)。因此,最好优雅地失败,并在适当时检查EPERM errno。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接