I therefore decided to create both a daemon module and a http server to test it.
Code availability
Both modules are available on GitHubThe daemon module is called daemon.py and the httpserver is called restrictedhttpserver.py. Both have a fair amount of comments in their source code that are the result of me trying to fully understand what is required to get things working.
A small word of warning here: I only tested it with Python3 on Ubuntu and Raspbian and I do not expect it to work on any other operating system (although most *nix like systems probably will work) and in my world Python2 is no longer a valid option.
Also, the Daemon
class makes use of an undocumented attribute in the logging.FileHandler class (the stream.fileno attribute) which is necessary in order not to close this stream when forking a child process. Theoretically this might break in the future but I did want all logging to happen outside the chroot jail so that the process running as a daemon can log but does not have access to the logging. Whether this is a valid approach remains to be seen as this also prevents proper log rotation.
The Daemon class
The basic requirement are all implemented, an instance of a Daemon class can:- run in a chrooted jail
- so it cannot access files outside a given directory
- run with lowered privileges
- so the risk of accessing files not owned by a specific user is lowered
- can have a umask of your choice
- which will ensure that newly created files by the daemon are not open to all
- log outside its jail
- so that we can log events without offering the process managed by daemon any way of altering the logging
- and maintains a configurable pid file
- so that we can prevent the daemon from starting more than once and providing a way the fond out the process id so that we can terminate the daemon
Daemon
class follow the Singleton pattern, i.e. there can only be one instanced object of the class. This is sensible because a process can only be daemonized once. We also implemented to required methods to facilitate use as a context manager. A typical, minimal code snippet would look something like:from daemon import Daemon dm = Daemon() with dm: ... do something forever ...All configurable options have sensible defaults, but a call could look like this:
from daemon import Daemon dm = Daemon(user='httpserver', rootdir='/var/www', umask=0o27, pidfile='/var/lockhttpserver.pid', logfile='/var/log/httpserver.log', name="Http Server") with dm: ... do something forever ...It is important to understand that when the Daemon object is created, the process is not immediately daemonized. This is handled by the context manager (or you could call the
daemonize()
method directly). There is also a stop()
method provided that will check for a running daemon process and terminate it. This could look like this:from daemon import Daemon import argparse parser = argparse.ArgumentParser(description="Example Daemon") parser.add_argument('-s', '--stop', action='store_true', help='stop a running server') args = parser.parse_args() dm = Daemon() if args.stop: dm.stop() else: with dm: ... do something forever ...