Dropping Root Permissions In Python

前端 未结 6 1897
南旧
南旧 2020-12-02 15:36

I\'d like to have a Python program start listening on port 80, but after that execute without root permissions. Is there a way to drop root or to get port 80 without it?

相关标签:
6条回答
  • 2020-12-02 15:56

    Most of this works unless you need to request the socket after you do some other stuff that you don't want to be superuser.

    I made a project called tradesocket a while ago. It allows you to pass back and forth sockets on a posix system between processes. What I do is spin off a process at the beginning that stays superuser, and the rest of the process drops down in permissions and then requests the socket from the other.

    0 讨论(0)
  • 2020-12-02 16:02

    It is not a good idea to ask the user to enter his/her user-name and group whenever I need to drop privileges. Here is a slightly modified version of Tamás's code which will drop privileges and switch to the user who initiated the sudo command. I am assuming you are using sudo (if not, use Tamás's code).

    #!/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())
    
    0 讨论(0)
  • 2020-12-02 16:03

    You won't be able to open a server on port 80 without root privileges, this is a restriction on the OS level. So the only solution is to drop root privileges after you have opened the port.

    Here is a possible solution to drop root privileges in Python: Dropping privileges in Python. This is a good solution in general, but you'll also have to add os.setgroups([]) to the function to ensure that the group membership of the root user is not retained.

    I copied and cleaned up the code a little bit, and removed logging and the exception handlers so it is left up to you to handle OSError properly (it will be thrown when the process is not allowed to switch its effective UID or 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)
    
    0 讨论(0)
  • 2020-12-02 16:07

    I recommend using authbind to start your Python program, so none of it has to run as root.

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

    0 讨论(0)
  • 2020-12-02 16:07
    1. systemd can do it for you, if you start your program through systemd, systemd can hand off the already-open listening socket to it, and it can also activate your program on first connection. and you don't even need to daemonize it.

    2. If you are going to go with the standalone approach, you need the capability CAP_NET_BIND_SERVICE (check capabilities man page). This can be done on a program-by-program basis with the correct command line tool, or by making your application (1) be suid root (2) start up (3) listen to the port (4) drop privileges / capabilities immediately.

    Remember that suid root programs come with lots of security considerations (clean and secure environment, umask, privileges, rlimits, all those things are things that your program is going to have to set up correctly). If you can use something like systemd, all the better then.

    0 讨论(0)
  • 2020-12-02 16:16

    The following is a further adaptation of Tamás's answer, with the following changes:

    • Use the python-prctl module to drop Linux capabilities to a specified list of capabilities to preserve.
    • The user can optionally be passed as a parameter (it defaults to looking up the user who ran sudo).
    • It sets all the user's groups and HOME.
    • It optionally changes directory.

    (I'm relatively new to using this functionality, however, so I may have missed something. It might not work on older kernels (<3.8) or kernels with filesystem capabilities disabled.)

    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)
    

    It can be used as follows:

    drop_privileges(user='www', rundir='~', caps=[prctl.CAP_NET_BIND_SERVICE])
    
    0 讨论(0)
提交回复
热议问题