Integrate with SystemD

Goals

One of the aims of having a vault is to protect your secrets and monitor access. This can be defeated if you copy the secrets from the vault in a local file on the disk (especially if you don’t precisely control who can access your file).

Additionally one of the popular methods of configuring application in the cloud-era is through environment variables.

Vault-cli aims at helping you launch your application with the secrets it needs without writing them on disk. This page lists a few scenarios that may be useful.

Let’s assume the value you need to pass is the value you get with:

$ vault-cli get mysecret value
ohsosecret

Passing secrets through environment

The first thing you need to figure out is if the process you’re trying to integrate supports configuration through environment variables.

  • This may be something they tell upfront in their documentation.

  • This may be something that can be achieved through specific configuration tools. For example, tools that let you write configuration in Python files (Sentry) or in dedicated languages like RainerScript (rsyslog).

  • This maybe something that is not well documented but that still exists. Official docker images for the application may be using those variables, like for Datadog.

  • (And in many cases, this is just not possible)

Assuming you have identified the proper enviroment variable, we will launch the program through vault-cli env. Let’s launch it as a one-off:

$ vault-cli env --envvar mysecret:value -- myprogram

This will make a variable named VALUE available to myprogram. See the vault-cli env dedicated page for more details on how you can fine-tune the environment variable names, recursively load secrets as environment variables etc.

Now, let’s integrate this with systemd. First, look at the existing ExecStart command:

$ systemctl cat myprogram.service
[Service]
...
ExecStart=myprogram --options
...

We’ll create an override file that will change ExecStart to wrap it in vault-cli:

$ sudo systemctl edit myprogram.service
# opens a new file for edition
[Service]
ExecStart=
ExecStart=vault-cli env --envvar mysecret:value=MYVAR -- myprogram --options

The empty ExecStart= tells SystemD to ignore the previous command to launch and only launch the following one.

Save and quit the file. Load you new configuration file with:

$ sudo systemctl daemon-reload
$ sudo systemctl restart myprogram.service

Writing secrets to files on the filesystem before start

In some cases, you will need to have a file in the filesystem that contains directly the secret. This is often the case with private keys.

Our strategy will be to mount a RAM drive when our process start, and have our drive be accessible only for the current process. The drive will disappear when the process terminates, and nothing will be written on disk.

In this case, we’ll also create a service override file. We’ll add a wrapper arount our program like before.

$ sudo systemctl edit myprogram.service
# opens a new file for edition
[Service]
TemporaryFileSystem=/private
ExecStart=vault-cli env --file mysecret:key=/private/path/to/secret/file -- myprogram --options

Save and quit the file. Load your new configuration file with:

$ sudo systemctl daemon-reload
$ sudo systemctl restart myprogram.service

You will need to configure myprogram to look for your secret file at /private/path/to/secret/file.

If you need several files, you can add more --file flags, as many times as needed.

Note

If you want to use ssh within your program, and it supports reading the key from ssh-agent, rather than writing the private key to the disk, you may want to have a look at the dedicated Use an SSH private key without writing it on the disk feature.

Bake secrets into a complex configuration file

Warning

It’s been reported that this approach doesn’t work as intended. It’s left for inspiration, but as of today, ExecStartPre cannot write to the private filesystem created by TemporaryFileSystem in way that ExecStart can later read. Please refer to the ticket for workarounds.

In some cases, the program you want to launch doesn’t accept configuration through environment but only through configuration files. You could be tempted to use the method above, but the configuration file mixes secrets and a lot of other information that should not be stored in the vault. In this case, you need a way to write your configuration file without secrets on disk and, at the last moment, to bake the secrets into the file. To do that we’ll use vault-cli template.

See the dedicated Render templated files with secrets documentation for detailed use of vault-cli template.

The integration strategy will depend of several factors:

  • Does``myprogram`` expect to read its configuration file at a specific location?

  • Does it accept an arbitrary configuration path?

  • Does the folder containing the configuration contain other files or just that configuration file?

We will be using a TemporaryFileSystem like above, but this option can only be used to make a folder, not a single file. If the configuration can be read anywhere or if the whole folder can be overridden, then it’s the easier path. Otherwise, you may want to create a symbolic link in place of your configuration file, that will be pointing to your temporary file system.

Let’s assume that myprogram will read its configuration at /private/myprogram.conf, through customization of the configuration file path or through a symbolic link in the standard configuration file location.

The systemd configuration will be close to our previous case:

$ sudo systemctl edit myprogram.service
# opens a new file for edition
[Service]
TemporaryFileSystem=/private
ExecStartPre=vault-cli template --input=/etc/myprogram/myprogram.conf.j2 --output=/private/myprogram.conf

Save and quit the file. Load you new configuration file with:

$ sudo systemctl daemon-reload
$ sudo systemctl restart myprogram.service

vault_cli as a python lib

Finally, if the program is made with Python and you control it, another solution can be to use vault_cli on the Python side, and load your secrets when your process starts. This does not follow 12 factors methodologies, and it means your program will be strongly coupled with the vault, which will make development more complicated.

See Use vault_cli inside a Python program.