twistedsystemdtwistdtwisted.application

How deploy Twistd https application (.tac) with systemd as unprivileged user?


My https(port 443) twistd application (.tac) works fine deployed as a systemd service but the unit file requires user:root to listen/bind ports below 1000. The problem is that twistd runs also as user:root.

How to listen/bind port 443 then hand-off to twistd .tac as an unprivileged user?

I’d like to follow “separation of privilege” best practice and avoid workarounds like setcap 'cap_net_bind_service=+ep' or port-forwarding as discussed in detail here.

I tried systemd using Socket Activation with a .service unit file. My .socket works to listen/bind on privileged port 443. And the .service file starts the twistd .tac application as non-privileged user, but the socket hand-off doesn’t work and twistd exits with “permission denied” error. After searching I found "Known issue: Twisted does not support listening for SSL connections on sockets inherited from systemd" last line of this Twisted doc. I use Twisted 18.9.0 ubuntu 18.04.

Partial success with the following .service and .socket files:

My Systemd service unit file:

[Unit]
Description=twistd https application
#Requires=testtls.socket

[Service]
ExecStart=/usr/bin/twistd --nodaemon --pidfile= --python=/ws/twistdhttps.tac
WorkingDirectory=/srv/web/https
#User=nobody   #twistd .tac permission denied
#Group=nogroup #twistd .tac permission denied
User=root   #twistd .tac works but no separation of privileges
Group=root  #twistd .tac works but no separation of privileges

Restart=always
#NonBlocking=true

[Install]
WantedBy=multi-user.target

Systemd socket file testtls.socket:

[Socket]
ListenStream=0.0.0.0:443

[Install]
WantedBy=sockets.target

Solution

  • I worked out a reverse proxy type solution with two systemd files, which I realize is an in-elegant way compared to handing off a socket from one systemd file. One of my .service files has a root user and the other a non-privileged user. The redirect .service file used twisted.web.util.redirect (latest document can be found here) to redirect 443 to 8443. The other .service file listens on port 8443 and most importantly as an unprivileged user.

    Tested and works fine, however, some with this same problem may wonder how this is different from port-forwarding because reverse proxy is just another type of workaround compared to a .socket tls handoff.

    Port-forwarding using iptables would work and as it is handled by the kernel it seems like it might be faster than the additional load of running a reverse proxy server. For my use case, I decided on reverse proxy as it adds an additional layer of security and it’s also easier to keep links intact on the proxy as outlined here.

    For the time being I’m accepting this as the best answer as it will help anyone else who encounters the same problem, but I hope someone posts a better, more elegant solution.