Connor's Blog

Protecting Secrets for Unprivileged systemd Services

Hey! I hope you’ve been staying safe in this pandemic. The UK is beginning to emerge again but it remains to be seen whether we’ll all have to put away our parasols and head back inside for a few months. This post’s about protecting secrets which you want to pass to an unprivileged systemd service. There’s no obvious way to do this and I haven’t seen any new features coming up in systemd to help.

One approach

For this, we’ll assume that we want to pass some secret as an environment variable to a process. A common approach for this is to store the secret in a file, and use the ExecStart= directive to execute a shell, declare the variable, and then exec your program:

ExecStart=/bin/bash -c "MY_SECRET=$(cat /etc/secret) exec coolprogram"

This might look okay right now, but can become messy when you have a lot of secrets. The other problem is file permissions, or more specifically, how you protect your secret files. If your service is running as root, sure, you could chown root and chmod 600 your secret file, but running a service as root is discouraged and shouldn’t be required just so that you can protect secrets properly. We could create a new user, specify that the service should run as that user with the User= directive, and chown $MYUSER and chmod 600 the secrets, but this is high-maintenance.

A (maybe) better approach

The DynamicUser= and EnvironmentFile= are a nice combination that allows you to protect your secrets without creating another user and without the service running as root.

DynamicUser=

EnvironmentFile=

So what we can do is create a list of environment variables and their associated secrets in a single file (VAR=VAL syntax), then protect it by doing chown root and chmod 600. Specify this file using EnvironmentFile= and systemd will read it when the process starts and pass the variables to the unprivileged process. The service will look something like this:

...
[Service]
DynamicUser=true
EnvironmentFile=/etc/secrets
...

Summary

DynamicUser= saves you from having to create a user for the service to run as. EnvironmentFile= resolves the permissions issue since systemd opens it rather than the process spawned by systemd, and it’s cleaner than writing out a list of variables inline in a service file. You can chown root and chmod 600 the secrets and systemd will have no problem passing the secrets to the unprivileged service.