检查脚本用户是否具有类root用户特权的最佳方法是什么?


74

我有一个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.")

这是进行检查的最佳方法吗?还有其他最佳做法吗?

Answers:


31

根据“比许可更容易请求宽恕”原则:

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


这是最可移植的方式,甚至可以在更特殊的情况下使用,例如SELinux限制根特权。
Florian Diesch 2010年

4
根据情况,这实际上可能更糟。嗯,但弗洛里安有一点。
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ 2010年

@Longpoke-您是指用户可能在/ etc中具有重命名权限,但是-说缺乏从该路径中删除文件的能力?如果那是您的意思,那么试图事先确定用户具备的功能是傻瓜的事。更好地为“事务性”语义构造代码,如果C失败,则A和B可以回滚。
msw 2010年

@msw:我最初以为,“您应该只检查您是否是root用户,然后退出”,以避免任何事务性后果,但是SELinux,ACL和功能肯定会破坏这种情况,所以我认为唯一的方法正确的是您的建议。
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ 2010年

1
有时,在开始进行更改之前,您需要了解任务的所有部分是否都将成功。我不想执行步骤1、2和3,只是为了找出无法执行的步骤4,并希望我可以退回已完成的操作。
keithpjolley

75

os.geteuid获取有效的用户ID,这正是您想要的,所以我想不出任何更好的方法来执行这种检查。不确定的一点是标题中的“ root-like”:您的代码检查的 root完全”,而不是“ like”,实际上我不知道“ root-like but not root”是什么意思-因此,如果您的意思不同于“完全扎根”,也许可以澄清一下,谢谢!


2
我假设Paul担心管理员使用不同uid的不同系统。通常id(root)== 0,但这不是必须的,在某些系统上,它实际上并不相等。
斯坦(Stan)2010年

@Stan:那么EAFP确实会更好
城市生活垃圾

8
有效UID == 0意味着“根”的假设已经深入到UNIX代码中(包括在大多数类似UNIX的内核源代码中)。从技术上讲,在Linux下,这种假设不一定正确。Linux“功能”模型允许系统与通过进程继承(lcap2包装器)或可能通过扩展文件系统属性委派的更细粒度的控件一起运行。SELinux功能在这种假设下也可能造成严重破坏。对于在那里的99%的系统,geteuid()== 0就足够了;其余的尝试:...除了:是您的朋友。
吉姆·丹尼斯

15

您可以提示用户进行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 acccess的程序的实例。您可以为此尝试类似的操作。
Mateo de Mayo

7

如果您确实希望您的代码在各种Linux配置中都具有鲁棒性,那么建议您考虑一些极端的情况,即有人在使用SELinux,文件系统ACL或Linux内核中已有的“功能”功能从v。2.2左右开始。您的进程可能正在使用SELinux的某些包装器下运行,或者在Linux功能库(例如libcap2 libcap-ngfscapselfcap)下通过诸如Niels Provos精彩而令人遗憾的被低估的systrace之类的东西运行系统。

所有这些都是您的代码可能以非超级用户身份运行的方式,但是您的进程可能已经被委派了执行EU工作的必要访问权限,而无需EUID == 0。

因此,我建议您考虑通过包装可能由于权限或异常处理代码的其他问题而失败的操作,以更Python的方式编写代码。如果您打算执行各种操作(例如使用subprocess模块),则可以为所有此类调用添加前缀sudo(例如,命令行,环境或.rc文件选项)。如果它是交互式运行的,则可以提供使用以下命令重新执行任何引发权限相关异常的命令sudo(仅当发现sudo在os.environ ['PATH']上,。

总体而言,大多数Linux和UNIX系统的大部分管理工作都是由“ root”特权用户完成的。但是,这是一门古老的书,作为程序员,我们应该尝试支持较新的模型。尝试操作并让异常处理发挥作用,可以使您的代码在可以透明地允许您进行所需操作的任何系统下工作,并且意识到并准备使用它sudo是一种不错的感觉(到目前为止,这是最广泛的应用用于控制系统特权的委派工具)。


6

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

如果不是os.environ.keys()中的“ SUDO_UID”:
  打印“此程序需要超级用户priv。”
  sys.exit(1)

这是简单而整洁的。谢谢!
blueren


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通用性。


1
将其编辑为您的其他答案更有意义吗?
jpmc26 2014年

2

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

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,并且您无法将strand连接在一起NoneType。如果您想使用它os.getenv("SUDO_USER")来解决问题,则应这样做:if os.getenv("SUDO_USER") == None: print "This program need 'sudo'"; exit()
或B

1
感谢您的更新,此代码将仅测试sudo,而不测试root用户访问权限。因为它不检查root,所以如果您以root身份登录(或像我这样写了cronjob),您仍然需要使用sudo调用脚本,因为sudo不需要root密码,因此我可以修复cronjob只用sudo调用我的python脚本。
塞迪迪

参加聚会有点晚,但这确实非常有用!当与结合使用时os.getenv('HOME'),这将告诉您当前用户是root还是sudo您的脚本通常需要知道的用户。
Prahlad Yeri

0

这完全取决于您希望应用程序具有多大的可移植性。如果您的意思是商务,我们必须假设管理员帐户并不总是等于0。这意味着仅检查euid 0是不够的。问题是,在某些情况下,一个命令的行为就像您是root一样,而下一个命令将由于权限被拒绝而失败(请考虑SELinux&co。)。因此,最好适当地尝试失败并检查EPERM errno。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.