【问题标题】:Dropping Root Permissions In Python在 Python 中删除 Root 权限
【发布时间】:2011-02-11 14:18:54
【问题描述】:

我想让一个 Python 程序开始侦听端口 80,但之后在没有 root 权限的情况下执行。有没有办法删除 root 或在没有它的情况下获取端口 80?

【问题讨论】:

  • 在现代 Linux 上,你只需要能力 CAP_NET_BIND_SERVICE 来绑定到端口 80,你不需要是 root,甚至在应用程序启动时也不需要。 Capabilities 是 POSIX 标准 1003.1e,它是将所有强大的 root 权限划分为一组不同的权限。参见:python-cap-ng 和 /sbin/setcap、/sbin/getcap(这些相当于 chmod setuid 和 ls -l)
  • 对于 Python2 和其他解释器来说,获得能力是你要小心的部分——libcap-ng 可以降低上限,但它不会授予它们。对 Ian 所引用问题的回答是一种相对安全的方式,可以为特定项目一次发放一个上限:stackoverflow.com/a/21895123/1724577

标签: python linux unix permissions root


【解决方案1】:

以下是对Tamás's answer的进一步改编,有以下改动:

  • 使用python-prctl module 将 Linux 功能删除到要保留的指定功能列表中。
  • 可以选择将用户作为参数传递(默认查找运行 sudo 的用户)。
  • 它设置所有用户的组和HOME
  • 它可以选择更改目录。

(不过,我对使用此功能比较陌生,所以我可能遗漏了一些东西。它可能不适用于较旧的内核 (

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])

【讨论】:

    【解决方案2】:

    当我需要放弃权限时,要求用户输入他/她的用户名和组不是一个好主意。这是 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())
    

    【讨论】:

    • 我不喜欢这个。此方法假定存在 SUDO_USER,仅当您通过 sudo 运行时才设置它。通过 systemd 启动或直接通过 root 运行时,它不适合放弃特权。是的,您可以通过设置 SUDO_USER=someone 使其工作,但这是一个 hack。守护进程通常以 root 身份启动,执行设置所需的特权工作,然后转到“nobody”或通过他们的日常工作配置指定的用户。
    【解决方案3】:

    除非您在执行一些您不想成为超级用户的其他事情后需要请求套接字,否则大部分方法都有效。

    不久前我做了一个名为tradesocket 的项目。它允许您在 posix 系统上的进程之间来回传递套接字。我所做的是在开始时分拆一个保持超级用户身份的进程,然后该进程的其余部分降低权限,然后从另一个请求套接字。

    【讨论】:

      【解决方案4】:
      1. systemd 可以为您完成,如果您通过 systemd 启动程序,systemd 可以将已经打开的侦听套接字交给它,它还可以在第一次连接时激活您的程序。你甚至不需要守护它。

      2. 如果您打算使用独立方法,则需要功能 CAP_NET_BIND_SERVICE(检查功能手册页)。这可以使用正确的命令行工具逐个程序完成,或者通过使您的应用程序 (1) 成为 suid root (2) 启动 (3) 监听端口 (4) 立即删除权限/功能.

      请记住,suid root 程序带有许多安全注意事项(干净和安全的环境、umask、特权、rlimits,所有这些都是您的程序必须正确设置的东西)。如果你可以使用类似 systemd 的东西,那就更好了。

      【讨论】:

        【解决方案5】:

        我建议使用authbind 来启动您的 Python 程序,这样就不必以 root 身份运行。

        https://en.wikipedia.org/wiki/Authbind

        【讨论】:

        • 一个如何使用authbind的例子 - goo.gl/fxFde6 - 用你喜欢的任何东西替换NodeJS(例如:python)
        • mutelight.org/authbind 的示例很好地解释了/etc/authbind/byport/80 的所有权/权限。
        【解决方案6】:

        如果没有 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)
        

        【讨论】:

        • 请记住,HOME 目录仍将是/root 而不是/home/uid_name,并且uid_name 将无法对~/ 执行任何操作,然后它将扩展为@987654330 @。这可能会影响像 matplotlib 这样将配置数据存储在 HOME 目录中的模块。 authbind 似乎是处理这个问题的正确方法。
        • 而且 HOME 变量不会是唯一仍然认为当前用户是 root 的变量。
        • “如果没有 root 权限,您将无法在端口 80 上打开服务器......” - 这不一定是真的(也许不再是?)。另请参阅 SuperUser 上的 Allow non-root process to bind to port 80 and 443?Is there a way for non-root processes to bind to “privileged” ports on Linux?
        猜你喜欢
        • 2011-03-22
        • 2013-03-20
        • 2018-03-14
        • 2018-08-16
        • 1970-01-01
        • 2018-10-03
        • 1970-01-01
        • 2015-06-22
        • 1970-01-01
        相关资源
        最近更新 更多