我有一个 Python 脚本,它将执行许多需要根级权限的操作,例如移动 /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.")
这是进行检查的最佳方式吗?还有其他最佳实践吗?
os.geteuid
获取有效用户 ID,这正是您想要的,因此我想不出任何更好的方法来执行此类检查。不确定的一点是标题中的“类根”:您的代码检查exactlyroot
,没有“喜欢”,实际上我不知道什么是“类根但不是根”意思是 - 所以,如果你的意思不同于“完全根”,也许你可以澄清一下,谢谢!
根据 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
混为一谈。
您可以提示用户进行 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
)。
对于 Linux:
def is_root():
return os.geteuid() == 0
我喜欢检查环境变量中的 sudo:
如果 os.environ.keys() 中不是“SUDO_UID”: 打印“这个程序需要超级用户权限。” 系统退出(1)
如果您真的希望您的代码在各种 Linux 配置中都具有健壮性,我建议您考虑一些极端情况,即有人可能正在使用 SELinux、文件系统 ACL 或 Linux 中的“功能”特性内核从 v. 2.2 左右开始。您的进程可能在使用 SELinux 的某些包装器或某些 Linux 功能库下运行,例如 libcap2 libcap-ng,或 fscaps 或 elfcap -赞赏systrace系统。
所有这些都是您的代码可能以非 root 身份运行的方式,但您的进程可能已被授予必要的访问权限以在没有 EUID==0 的情况下执行其工作。
所以我建议您考虑通过包装可能因权限或异常处理代码的其他问题而失败的操作,以更 Python 的方式编写代码。如果您正在执行各种操作(例如,使用
subprocess
模块),您可能会为所有此类调用添加前缀 sudo
(例如,作为命令行、环境或 .rc 文件选项)。如果它以交互方式运行,您可以使用 sudo
重新执行任何引发权限相关异常的命令(仅当您在 os.environ['PATH'] 上找到 sudo
时才可选)。
总的来说,大多数 Linux 和 UNIX 系统的大部分管理工作仍然由“root”特权用户完成。然而,这是老派,作为程序员,我们应该尝试支持更新的模型。尝试你的操作并让异常处理完成它的工作允许你的代码在任何透明地允许你需要的操作的系统下工作,并且知道并准备好使用
sudo
是一个很好的接触(到目前为止,最广泛的系统权限受控委派工具)。
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
运行则不可用。
(抱歉评论框太小了)
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
现在我们回到异常处理。我将拒绝评论 subprocess 模块的类 Java 过度通用性。
我的应用程序使用此代码:
import os
user = os.getenv("SUDO_USER")
if user is None:
print "This program need 'sudo'"
exit()
这完全取决于您希望应用程序的便携性。如果您指的是商业,我们必须假设管理员帐户并不总是等于 0。这意味着检查 euid 0 是不够的。问题是,在某些情况下,一个命令的行为就像您是 root 用户一样,而下一个命令将失败并显示 permission denied(想想 SELinux & co.)。因此,最好优雅地失败并在适当的时候检查 EPERM errno。