How bad is manually deploying secrets?

I’m planning to use NixOS for a single home server, its services require a handful of secrets, and I’m trying to decide how to manage the secrets.

The simplest approach I see is to just specify absolute paths to secrets files in my configuration and then manually/out-of-band deploy those secrets when bringing up the server. Obviously, this relies on external state, isn’t reproducible, etc., but the upside is that it’s just a quick scp and I’m done.

How bad of an idea is this? What gotchas do I need to look out for?

While a wiki comparison of secret managing schemes says “this paradigm is still available to NixOS users” (and nix.dev corroborates), I’m worried that there are some details that I’m missing when opting for this route.

I don’t have a ton of experience with Nix, but my main concerns are:

  • If the path ever gets concatenated with a string (e.g. when building some config file?), the file will get copied to the world-readable Nix store – is this likely? This one actually worries me quite a bit!
  • The file could also get copied at activation time by a script – I’m just speculating here
  • The many possible “unknown unknowns”, since I’m a relative newbie

Other than inconvenience or being old fashioned, do you see other problems with this approach? I think this is fine and no worse than doing the same thing without Nix, but am I wrong? (I searched through a bunch of threads, wiki pages, and blog posts, and couldn’t find a direct answer to this concern.)

It would mostly only happen if you write such code in your config. So, simply don’t write code like that :sweat_smile: The only exception would be if you provide a secrets file path in a really badly written 3rd-party module (I’ve never seen nixpkgs itself screw up this part), but again, you could just not provide that path or not use 3rd party modules and avoid the issue entirely.

If you’re deploying the secrets outside of your config, and the scripts you write are not part of your config either, it’s nigh impossible for that to happen.

If you write an activation script to do that, sure.

I personally encrypt my secrets with sops and use sops-nix to deploy the plain secret at runtime - which does not put the plain secret in the store, only the encrypted secret, and decryption happens as part of activation. I personally find that acceptable for my threat model, but you may not; you can of course continue to use non-nix-dependent methods of secrets deployment with effectively the same risks/benefits as any other Linux system.

2 Likes

Ah, looks like you just edited your post. Yes, my concern was around services that I pull in from nixpgs (I should have said that). It sounds like you think it’s unlikely a service in nixpkgs would do that – this is the sort of reassurance I was looking for. The worst case that I had in mind was that I’d need to manually inspect each service I pulled in via nixpkgs, to make sure it didn’t do a path concatenation somewhere.

I don’t want to claim that it’s impossible for nixpkgs to screw it up, since people do make errors, but since a much smaller subset of people can merge PRs (and committers tend to only merge PRs that they’re comfortable with reviewing), I think it’s unlikely for nixpkgs to screw it up.

Still, if you’re not providing any secrets paths to those modules’ options, then the quality of said modules doesn’t matter, since your config therefore wouldn’t have access to your secrets. What you’re doing now is the easiest way to avoid trust issues.

1 Like

For a relatively simple secrets setup i can recommend using agenix: Basically you pre-encrypt all your secrets with the public key of your server (so that the server will be able to decrypt them later), and deploy them as usual Nix files. The server (using agenix) will decrypt them and mount them under a preconfigured path for your service.

In principle, you could evaluate your config under a user that cannot read the secrets?

If upstream secret management is bad enough, this might even be necessary (to create a config with secrets embedded into it); I guess watching what is being run and does it set restrictive permissions on relevant directories and files is unavoidable at some level.

I also hope the secrets are service-specific, so if one low-impact module has an issue, this doesn’t compromise modules with more importance but also more attention!

2 Likes

AFAIK, this would only happen if you use a bare path type, e.g let mySecret = /var/secrets/my_secret; , which you do not want to do and may not even work in pure eval mode. If you instead specify your secret paths as strings (let mySecret = “/var/secrets/my_secret” then concatenation will never accidentally copy the file into the store.

You can enable lint-absolute-path-literals in your nix config file to warn you if you’re using path literals that could accidentally copy files from your host machine.

3 Likes

Good point! That is my understanding as well.

I cannot think of more unknown unknowns than everything that has been discussed here.

Also I can’t think of any gotchas in sending secrets manually to the host over scp. Of course, if you activate before sending the secrets or if they don’t have the correct permission, the systemd services using them will not start.

2 Likes