0

What is the best-practice way to write a python application where the majority of the code can run as the normal, non-root user -- but where there is at least one function that requires root permissions to execute?

I'm writing a python program where 99% of it can run fine in user-space. But I'm now adding a function that requires root. One solution is to just run the whole application as root. But that seems unnecessarily dangerous.

I figured "the right thing to do" would be to spawn some child process (or thread?) that:

  1. Has root permissions
  2. Only includes the functions that absolutely require root privileges
  3. Accepts no user input (or at least as little as possible and carefully sanitizes it under a very strict allowlist regex)

But I'm not exactly sure how to do all of this. And maybe there's other best-practices that I'm not considering?

Consider the following example code.

Note I'm just using sockets because binding to a port >= 1024 can be done as a normal user, but binding to any port < 1024 requires root privileges. But if there's a simpler example, please use it.

#!/usr/bin/env python3
import socket

def main():

    user_operation()
    root_operation()

def user_operation():
    sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
    sock.bind( ('localhost', 2222) )

def root_operation():
    sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
    sock.bind( ('localhost', 22) )

if __name__ == "__main__":
    main()

If I run the above program as the normal, unprivliged user then I get a Permission denied error

user@host:~$ ./spawn_root_child.py 
Traceback (most recent call last):
  File "/home/user/tmp/python_spawn_root_child/./spawn_root_child.py", line 18, in <module>
    main()
  File "/home/user/tmp/python_spawn_root_child/./spawn_root_child.py", line 7, in main
    root_operation()
  File "/home/user/tmp/python_spawn_root_child/./spawn_root_child.py", line 15, in root_operation
    sock.bind( ('localhost', 22) )
PermissionError: [Errno 13] Permission denied
user@host:~$

But if I run it as root, it runs fine

user@host:~$ sudo ./spawn_root_child.py 
user@host:~$

How can I update the above code so that it follows security best-practices, including the principle of least privilege?

Edit I'm looking for something that is:

  1. Robust (it would apply to small CLI scripts as well as large GUI applications)
  2. Cross-Platform (it should work in Linux, Windows, MacOS, and my python-powered toaster)
  3. Pythonic (following Python's best-practices)
Michael Altfield
  • 2,083
  • 23
  • 39
  • You should consider to run your `root_operation` first and then [drop the privileges](https://stackoverflow.com/a/2699996/3929826). – Klaus D. Oct 02 '22 at 05:40
  • ok, but what if `root_operation()` needs to be executed at undetermined times in the future? I need it to somehow stay available in the background. – Michael Altfield Oct 02 '22 at 06:22
  • If you keep your privileges just for "later", it won't be security best practice. And binding to a socket is a operation that you have to do only once. – Klaus D. Oct 02 '22 at 09:49

1 Answers1

1

If you are running your Python program in Linux, you can write a bash script and execute your Python code from inside it.

You can run your script as a non-root user, then raise privileges to root via sudo python3 script.py

Using sudo only in front of commands that require root is the best way because Linux will switch back to the regular user this way. (The worst way in my opinion is using sudo su in which you would need to execute exit to manually leave root!)

I would suggest separating your code into separate scripts, ones that do not require root and the one that needs root. This way you do not need to unnecessarily need to run sudo script.py for scripts that do not need root.

asultan904
  • 189
  • 7