I am deploying Goldfish, an interface for Vault, in production on a server dedicated to secrets management. So security is of prime concern here.
I am trying to deploy the service with systemd on an Unbuntu 16.04 system, giving it the least possible permissions. I want it to run as the non-root user goldfish
and to listen on port 443. I have tried multiple options.
Solution 1: using a systemd socket, as suggested in multiple posts I've found such as this one (Goldfish backend is written in Go as in the post). This seems the most precise and secure as it is managed entirely by systemd and opens a single port for a single service. My unit files look like this:
/etc/systemd/system/goldfish.socket
:[Unit] Description=a Vault interface [Socket] ListenStream=443 NoDelay=true # Tried with false as well
/etc/systemd/system/goldfish.service
:[Unit] Description=a Vault interface After=vault.service Requires=goldfish.socket ConditionFileNotEmpty=/etc/goldfish.hcl [Service] User=goldfish Group=goldfish ExecStart=/usr/local/bin/goldfish -config=/etc/goldfish.hcl NonBlocking=true # Tried with false as well [Install] WantedBy=multi-user.target
Unfortunately I get a "listen tcp 0.0.0.0:443: bind: permission denied". So it seems that it still doesn't get the desired permission, which seems to defeat the purpose of creating a socket for the service. What am I missing here?
Solution 2: giving the CAP_NET_BIND_SERVICE capability to the service. This time I use no systemd socket but the AmbientCapabilities
setting in the service (also tried with CpabilityBoudingSet
and Capabilities
, the latter being surprisingly undocumented in systemd.exec
). Adding the CAP_NET_BIND_SERVICE capability to the service should give the service the right to bind to any privileged port.
/etc/systemd/system/goldfish.service
:[Service] User=goldfish Group=goldfish ExecStart=/usr/local/bin/goldfish -config=/etc/goldfish.hcl # Tried combinations of those: AmbientCapabilities=CAP_NET_BIND_SERVICE #CapabilityBoundingSet=CAP_NET_BIND_SERVICE #Capabilities=CAP_NET_BIND_SERVICE+ep
Still no luck, I always end up with "listen tcp 0.0.0.0:443: bind: permission denied". What am I missing about capabilities management with systemd?
Solution 3: giving the CAP_NET_BIND_SERVICE capability to the executable. This time I give the CAP_NET_BIND_SERVICE capability directly to the goldfish executable, and nothing in the systemd service:
sudo setcap cap_net_bind_service=+ep /usr/local/bin/goldfish
Hurray, it works! The service correctly binds to port 443 and I can use it. However, I now have a binary that can be executed by any user and bind to any privileged port, which I don't really like. What is the relationship between executable capabilities and systemd service capabilities? Shouldn't systemd allow to achieve the same result that I get here but only for a specific process?
Solution 4: putting the service behind a proxy. The solution I'm considering now is to put Goldfish behind an nginx proxy that will be the only one visible from the outside and listening on port 443. This seems like a good compromise as it keeps tight permissions, but it adds a new piece to the setup, with what it adds of complexity and error potential. Does it seem like a superior or lesser option in terms of security and system administration?
So I actually have multiple questions here, but all relating to making a systemd service run as an unprivileged user while listening on a privileged port, and the security implications that it has. Did someone already encounter the same issues?
Thanks for your help!