Module to automatic encrypt secrets provided as files

Hi,

I need to distribute some secrets as files (wireguard secrets, tls private encryption keys) to several hosts, I don’t want to “send” the keys (as nixops is doing) as I would like to have the hosts be resilient to reboots (i.e. I don’t want to the secret to be deleted after a reboot).
The most promising option I see is encrypting the files using ace and the host ssh key, but I haven’t found something “convenient” to use. For convenient I mean, I would like to provide a list of files and for each of them indicate the target host/group/permission and have this file automatically encrypted with the correct key; then have a service that decrypt them at activation.

I checked mainly agenix that is very close to what I would like to have, but it seems to me that I need to pass the secret manually to agenix -e, while I would like to have some kind of module that I can just configure with the list of files and hosts, something like https://xeiaso.net/blog/nixos-encrypted-secrets-2021-01-20 but without relying on Nixops.

Is something existing that I missed or do I need to create the module by myself?

Thanks!

1 Like

Assuming you are using flakes, if your unencrypted secret values are declared outside of the repo then your flake will not be pure. If you want your flake to be pure, then you would have to commit your secrets to your repo, which isn’t great either.

The reason the agenix encryption command is separate from the nix build step is so that the encrypted values can be declared in the repo and purity is maintained.

You could use another tool like git-crypt or plain sops to hide secrets in the repo and decrypt them after cloning, but that workflow adds extra steps as well.

2 Likes

Note that this still creates world-readable files in the nix store, and should therefore typically be avoided.

1 Like

The reason the agenix encryption command is separate from the nix build step is so that the encrypted values can be declared in the repo and purity is maintained

I understand the desire of purity and I don’t use flakes, but I see that is pretty common to pass a filepath as an option, like this

users.mysql.passwordFile

why can’t we just do the same pattern, passing a list of secrets (+ attributes) to a module, encrypt them using age and save them in the store at build time?

1 Like

Because to process anything in any way, nix first copies your inputs to the nix store. The moment you hand something to nix for evaluation, it will be available in a world-readable state. You don’t want this to happen with secrets.

This is a deliberate design decision, as nix fundamentally demands pure evaluation of all of its build processes. Just like you can’t ask nix to run gcc on /opt/mylib.c at build time, you can’t ask it to run gpg on /var/secrets/password.txt.

This purity is kind of the entire point of nix, and why flakes are moving towards not permitting access to random files on the file system at evaluation time at all; if people just depend on random things it is impossible to guarantee that the project will still be buildable on another system. Even if each derivation individually in theory knows where its inputs came from, you won’t know if someone’s forgotten to actually share those inputs until you evaluate their project.

Hence, the solution the community has arrived at, is to encrypt secrets before passing them to nix, and then decrypting them at runtime.

Personally, I really don’t think the upfront effort of encrypting your secrets is high enough that it warrants breaking purity. If you really hate that you have to run individual commands for each secret, consider sops-nix - it uses sops as a backend, which stores all your secrets in a single, encrypted .yaml file, and has a feature that lets you edit this file in an editor as if it was plaintext. This sounds pretty close to what you’d like.

Just to be explicit about this; these passwordFiles are usually imperative, i.e., it will not be part of the nix evaluation. You’re in charge of making sure that file is in place when the system is actually running - usually using either agenix or sops-nix.

If you actually do make them part of the nix evaluation (using e.g. writeTextFile) without first encrypting them I’d recommend that you stop doing so asap and cycle those secrets.

If you just set them to strings, well, nix doesn’t actually touch those files. It just writes some configuration file for another application to tell it where the secrets should be at runtime. You need to make sure they’re actually there for the system to work, nix cannot know at build time if they’re missing.

3 Likes

thanks for the detailed explanation! I forgot that also the source input is added to the store and I saw that also the article I linked is encrypting the secrets outside the nix build step.

My use case is about automatically generated private keys via let’s encrypt that requires DNS access (because they are wildcard certificates) and I don’t want to distribute the dns provider access keys to the final hosts: in this case the secret is updated automatically, but I will create an external step, via Makefile or other, to simply encrypt the secrets if they are changed and add them to the nix build process already encrypted.

2 Likes