我想让一个 Python 程序开始监听端口 80,但之后在没有 root 权限的情况下执行。有没有办法删除root或在没有它的情况下获取端口80?
6 回答
如果没有 root 权限,您将无法在端口 80 上打开服务器,这是对操作系统级别的限制。因此,唯一的解决方案是在打开端口后放弃 root 权限。
这是在 Python 中删除 root 权限的可能解决方案:Droppingprivilege in Python。一般来说,这是一个很好的解决方案,但您还必须添加os.setgroups([])
到函数中以确保不保留 root 用户的组成员身份。
我稍微复制并清理了代码,并删除了日志记录和异常处理程序,以便您OSError
自行正确处理(当不允许进程切换其有效 UID 或 GID 时,它将被抛出):
import os, pwd, grp
def drop_privileges(uid_name='nobody', gid_name='nogroup'):
if os.getuid() != 0:
# We're not root so, like, whatever dude
return
# Get the uid/gid from the name
running_uid = pwd.getpwnam(uid_name).pw_uid
running_gid = grp.getgrnam(gid_name).gr_gid
# Remove group privileges
os.setgroups([])
# Try setting the new uid/gid
os.setgid(running_gid)
os.setuid(running_uid)
# Ensure a very conservative umask
old_umask = os.umask(077)
我推荐使用authbind
来启动你的 Python 程序,所以它都不需要以 root 身份运行。
每当我需要放弃权限时,要求用户输入他/她的用户名和组不是一个好主意。这是 Tamás 代码的略微修改版本,它将放弃特权并切换到启动 sudo 命令的用户。我假设您正在使用 sudo(如果没有,请使用 Tamás 的代码)。
#!/usr/bin/env python3
import os, pwd, grp
#Throws OSError exception (it will be thrown when the process is not allowed
#to switch its effective UID or GID):
def drop_privileges():
if os.getuid() != 0:
# We're not root so, like, whatever dude
return
# Get the uid/gid from the name
user_name = os.getenv("SUDO_USER")
pwnam = pwd.getpwnam(user_name)
# Remove group privileges
os.setgroups([])
# Try setting the new uid/gid
os.setgid(pwnam.pw_gid)
os.setuid(pwnam.pw_uid)
#Ensure a reasonable umask
old_umask = os.umask(0o22)
#Test by running...
#./drop_privileges
#sudo ./drop_privileges
if __name__ == '__main__':
print(os.getresuid())
drop_privileges()
print(os.getresuid())
systemd 可以为您完成,如果您通过 systemd 启动程序,systemd 可以将已经打开的侦听套接字交给它,并且它还可以在第一次连接时激活您的程序。你甚至不需要守护它。
如果您打算使用独立方法,则需要功能 CAP_NET_BIND_SERVICE(检查功能手册页)。这可以使用正确的命令行工具逐个程序完成,或者通过使您的应用程序 (1) 成为 suid root (2) 启动 (3) 监听端口 (4) 立即删除权限/功能.
请记住,suid root 程序带有许多安全考虑(干净和安全的环境、umask、特权、rlimits,所有这些都是您的程序必须正确设置的东西)。如果您可以使用诸如 systemd 之类的东西,那就更好了。
以下是对Tamás 答案的进一步改编,有以下变化:
- 使用该
python-prctl
模块将 Linux 功能拖放到要保留的指定功能列表中。 - 用户可以选择作为参数传递(默认查找运行的用户
sudo
)。 - 它设置所有用户的组和
HOME
. - 它可以选择更改目录。
(但是,我对使用此功能比较陌生,所以我可能遗漏了一些东西。它可能不适用于较旧的内核(<3.8)或禁用文件系统功能的内核。)
def drop_privileges(user=None, rundir=None, caps=None):
import os
import pwd
if caps:
import prctl
if os.getuid() != 0:
# We're not root
raise PermissionError('Run with sudo or as root user')
if user is None:
user = os.getenv('SUDO_USER')
if user is None:
raise ValueError('Username not specified')
if rundir is None:
rundir = os.getcwd()
# Get the uid/gid from the name
pwnam = pwd.getpwnam(user)
if caps:
prctl.securebits.keep_caps=True
prctl.securebits.no_setuid_fixup=True
# Set user's group privileges
os.setgroups(os.getgrouplist(pwnam.pw_name, pwnam.pw_gid))
# Try setting the new uid/gid
os.setgid(pwnam.pw_gid)
os.setuid(pwnam.pw_uid)
os.environ['HOME'] = pwnam.pw_dir
os.chdir(os.path.expanduser(rundir))
if caps:
prctl.capbset.limit(*caps)
try:
prctl.cap_permitted.limit(*caps)
except PermissionError:
pass
prctl.cap_effective.limit(*caps)
#Ensure a reasonable umask
old_umask = os.umask(0o22)
它可以按如下方式使用:
drop_privileges(user='www', rundir='~', caps=[prctl.CAP_NET_BIND_SERVICE])
除非您在做一些您不想成为超级用户的其他事情之后需要请求套接字,否则大部分工作都有效。
前段时间我做了一个名为tradesocket的项目。它允许您在 posix 系统上的进程之间来回传递套接字。我所做的是在开始时分拆一个保持超级用户身份的进程,然后该进程的其余部分下降权限,然后从另一个请求套接字。