Today, I’m going to show you how to drop from the root user to an unprivileged user in Python for the purpose of running a Tornado app. First make a system user for your project to run as. In my example, I’ll be using projectuser as the username. Creating this user can be done like so:
sudo useradd --system --user-group projectuser
Now, in your script that is responsible for starting your Tornado app, you likely have something that probably looks like the following:
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(port)
tornado.ioloop.IOLoop.instance().start()
What we need to do now is define a user to run as and then drop privileges using a call to setuid. We can do this by replacing the above with:
if __name__ == "__main__":
import os
import pwd
# define user to run as
run_as_user = "projectuser"
# drop privileges
uid = pwd.getpwnam(run_as_user)[2]
os.setuid(uid)
# start tornado app
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(port)
tornado.ioloop.IOLoop.instance().start()
And voila, your app should now run as the user you defined! Do note that only the root user can call setuid. As a result, your script now needs to be run using sudo or from an upstart startup script, for example.
One caveat is that you won’t be able to use port numbers below 1024 since you are dropping to an unprivileged user before binding to the port. I think there’s a way to get around this by replacing http_server.listen() with http_server.bind(), http_server.start(), and dropping privileges between those calls, but this remains untested for now. Alternatively, you could use the respective proxy modules for Lighttpd or nginx to listen on privileged ports.

You might want to read the following paper (Setuid Demystified): http://www.eecs.berkeley.edu/~daw/papers/setuid-usenix02.pdf
Bookmarked and saved, I will read this soon!
George,Are there any sections of the paper Setuid Demystified that I should look over in regards to this blog post? I still plan on reading the paper but my reading queue is backed up a bit at the moment
have you tried calling the setuid after .listen() but before .start() ?
@Japhy, I hadn’t, but I just tried now. socket.error gets raised with permission denied as the message
I’m able to start as root and bind to <1024 before dropping UID.
My starting code with process forking…