我想让Python程序开始侦听端口80,但之后执行没有root权限。有没有办法放弃root或没有它的80端口?
答案 0 :(得分:55)
如果没有root权限,您将无法在端口80上打开服务器,这是对操作系统级别的限制。因此,唯一的解决方案是在打开端口后删除root权限。
以下是在Python中删除root权限的可能解决方案:Dropping privileges 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)
答案 1 :(得分:12)
我建议使用authbind
启动Python程序,因此不必以root身份运行。
答案 2 :(得分:7)
当我需要删除权限时,要求用户输入他/她的用户名和组并不是一个好主意。这是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())
答案 3 :(得分:4)
systemd可以为你做,如果你通过systemd启动你的程序,systemd可以将已经打开的监听套接字交给它,它也可以在第一次连接时激活你的程序。你甚至不需要守护它。
如果您要使用独立方法,则需要CAP_NET_BIND_SERVICE(检查功能手册页)功能。这可以通过正确的命令行工具逐个程序地完成,或者通过使您的应用程序(1)成为suid root(2)启动(3)立即监听端口(4)删除权限/功能
请记住,suid root程序有很多安全注意事项(干净安全的环境,umask,特权,rlimits,所有这些都是你的程序必须正确设置的东西)。如果你可以使用像systemd这样的东西,那就更好了。
答案 4 :(得分:2)
除非你在做一些你不想成为超级用户的其他东西之后需要请求套接字,否则大部分工作都有效。
我刚才创建了一个名为tradesocket的项目。它允许您在进程之间的posix系统上来回传递套接字。我所做的是在开始时分离一个保留超级用户的进程,然后进程的其余部分将权限下降,然后从另一个进程请求套接字。
答案 5 :(得分:2)
以下是对Tamás's answer的进一步修改,并进行了以下更改:
python-prctl
module将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])