Having trouble wrapping my head around nix-sops

I have an old cronjob script I’m converting into a Home Manager module, using pkgs.writeShellScript, to create a systemd service.

Problem is, the script runs a program that expects passwords and secrets to be present as environment variables, and the script currently contains those values. I don’t want to commit it to my Home Manager repo with those in there, so I need to separate them out in some way.

From what I’ve read, nix-sops seems to be how most people are handling the encryption of secrets. However, reading through the instructions for using it is creating more questions than answers for me:

  1. Write a .sops.yaml file, containing keys (new keys?), and rules for what encryption programs to use for each. I have a single use case, I’m guessing this is designed for multi-machine deployment?
  2. Looks like I need to install age to handle the encryption. SSH keys aren’t supported well and I don’t know what SSH keys it would want to use (I thought we were generating a new key here?), and GPG seems to be discouraged despite the README talking a great deal about how to use it.
  3. Run sops to create a new secrets file, presumably using the .sops.yaml file, though I don’t know in what way. In this file goes… I don’t know? Is this where I create the list of actual secrets? The examples given look random. How does sops know what to interpret in there? Do I put secrets in here, or does this file just define a structure for something else?
  4. Deploy… No idea how. Document mentions how if you’re using SSH keys, which I previously read to not be ideal. Also I’m using Home Manager, but that section of the documentation doesn’t explain much.
  5. Looks like within the Nix module, I can access the decrypted values via config.sops.secrets.<something>, though the examples always refer to a path which makes it sound like the decrypted value is located in a file rather than directly provided to the module.
  6. If I’m using nix-sops entirely within Home Manager, when does the decryption take place? Can I always assume a given secret is available at a given path, or does the request from a Nix module somehow trigger it?

I’ve tried searching for other examples and blog articles, but they either don’t use Home Manager (using nix-sops as a NixOS module is apparently completely different) or they assume I know what I’m doing.

Is nix-sops the preferred method of encrypting secrets contained within Nix modules? Is there something simpler for someone like me unfamiliar with sops, or perhaps other resources for explaining how nix-sops is supposed to work (and why, not just “do this”)?

4 Likes

Personally I found sops-nix (not nix-sops, I think you’ve got that inverted) not that complex. It took me a few hours to digest, admittedly, but it’s overall quite sensible. Maybe give it a proper try?

Alternatively there is also agenix, which is basically sops-nix but without the complexity of sops, and no gpg support. If you prefer age anyway it’s probably the way to go.

I do have a couple of answers to questions:

I’ll use “passwords” to mean the secrets you don’t want plaintext in your nix store/repository, and “keys” for the keys you need to create to use sops to explain this.

Basically, sops encrypts a file that contains all your passwords, and then has a little script that runs when your system is “activated” to decrypt the file and place the passwords stored in it in the paths you configured (i.e., on boot or whenever you run nixos-rebuild switch or home-manager switch).

To do this, you need a key for yourself to encrypt that file during development, and a different key for the host to decrypt it again on activation.

sops is the mechanism for managing these keys, and you’re right that it has nice provisions for projects with multiple developers and multiple deployment targets… It gives you fine-grained control over who can access which secrets, and what targets can decrypt them.

You can practically ignore those features though, they don’t add much complexity - you need two keys anyway.

You don’t. One of the benefits of sops-nix over agenix is SSH/GPG support.

The SSH key used is the openssh server key of the deployment target. It’s intended for servers, but if you don’t run openssh, you can just generate a GPG/age key. For the deployment target GPG is discouraged indeed because it’s a bit tricky to make function during boot, but I don’t think this is an issue for home-manager setups.

Your development key can be whatever you already use, so likely GPG. I like sops-nix because I can use my yubikey with it, age doesn’t support smartcards.

The .sops.yaml file just configures what keys sops will use. You don’t create it with sops, it needs to exist before you run it. Here is mine for example.

Yes, the file edited with sops contains your actual secrets. You just write a yaml file that contains your secrets, e.g.:

cron-secret: <mysupersecretpassword>

That’s it. Sops will encrypt that, nix will place it in the nix store, and then at boot sops will decrypt it and parse the yaml.

The sops-nix module then allows you to configure (in your nix system configuration) something like this:

sops.secrets."cron-secret".mode = "0400";

This will make the sops module look for the secret named “cron-secret” in the decrypted sops file, and then create a file containing just the string it is set to with the 0400 mode.

Elsewhere in your config you can then get a path to it something like this:

my.cron.script = ''
    cat ${config.sops.secrets."cron-secret".path} | curl
'';

The actual file will be somewhere in /run/secrets, the .path attribute basically just exists to abstract that and make it easy to change without updating your configuration.

My wireguard config shows it all off quite well, I think.

I’ve never used it with home manager. If you use the system ssh key, it will automagically work (the systemd unit will just read your system’s ssh key and decrypt the file using that). In other situations you probably need to have the system secret be readable by the systemd unit that ultimately runs the whole decryption process.

The home-manager stuff is still quite new, so the docs are likely lacking. I’d write an issue upstream about that, and read the code to figure it out in the mean time.

Yes, they are located in a file. Files are the safest method of delivering a key to a process. Almost every other method (cli args, env vars, …) will leak the key somewhere easily read by processes that shouldn’t have access to it.

We can thank the 80’s OS designers, the subsequent developer’s complacency, and all our collective reliance on legacy software and continued lack of standardization for this piece of poor UX.

Maybe systemd’s LoadCredential can move the needle for this in the future, I think it can pass it through a library call?

Again, I don’t know exactly how and when it runs. Likely when your user logs in, though. You should be able to rely on that file existing while your user is logged in.

Pretty much. Alternatives are the aforementioned agenix or systemd’s LoadCredential (maybe worth switching from cron to systemd timers?).

I’m not aware of any other resources, and yeah, the home-manager integration is very new, you’re likely one of the first to try it besides the original contributor. I agree the official readme is a bit… clunky, too. Maybe I should write a little uncomplicated guide for it (and the other secret deployment systems, including systemd’s) at some point.

2 Likes

Amazing response, I appreciate it! I eventually did get it working, with Home Manager, but I feel like my understanding is still a bit thin.

This may be part of my confusion regarding the use of SSH keys here. My understanding was that, traditionally, the SSH key was for the user, created by the user, and shared with a machine to prove trust. For a SSH key to be generated just for a given machine… I don’t understand what purpose that would serve.

So for sops, needing a key to encrypt secrets for a particular user on a particular machine, I guess that would be the same SSH key the user uses to access that machine? The documentation keeps referring to the machine’s SSH key, so I felt I was missing something.

How deep does that support go? In the instructions, it looked like it required the SSH key to have no password, so that it could be converted to an age key.

I’ve only briefly looked at agenix, I’d have to try it to fully understand it. But I had assumed sops-nix replaced it since I read several mentions of people migrating from agenix to sops-nix. Perhaps this isn’t the case. Maybe they switched purely to use SSH keys.

But if it’s simpler… it may be work using.

Understandable. I got it working, but I don’t know how well it would work with SSH keys. For now it’s using an age key created just for it, so it’s able to run in a user service just fine. One annoyance is that the sops-nix Home Manager config looks for secrets files relative to the Home Manager flake directory (or else go impure), so I have the sops config and age key mixed in with my nix modules. Not bad, but not ideal.

Now this looks interesting! I’ve never heard of systemd credentials before. The cron script I’m migrating is indeed wrapped in a systemd user service, tied to a systemd timer, so this might be a good choice. I’ll have to do more digging to see how much of the sops-nix setup it can replace.

1 Like

Ah, yeah, you’re still missing something crucial here; ssh uses public key encryption. i.e. both sides, the server and client, need a public and private key.

When you first start the ssh server, if there is no private key for the host yet, it will generate one.

When you then connect to a server for the first time (after adding your public key to the list of authorized keys for your user), your ssh client will show you a prompt asking you if you want to trust the server’s key. You’re supposed to go and check if the public key matches what the server owner tells you the server’s public key is, but most people just ignore it.

If the server’s private key is ever deleted and you try to connect, ssh will scream at you that the server key changed and say something about fingerprints. So far you’ve probably just deleted the entry from your known_hosts file without thinking twice about it.

So, 1. that’s the ssh key sops is talking about and 2. maybe make a backup of your ssh servers’ keys from now on so you don’t break your public key encryption anymore :wink:

My experience is that it just works™ for deployment keys with the NixOS module. Those ssh keys have to be passwordless for the ssh server to work anyway, I usually protect them through full-disk-encryption instead (the alternative would be typing like 10 passwords at boot, which really doesn’t work well with remote servers that haven’t started ssh yet…).

Given your user likely doesn’t have access to the ssh server’s private key though, if such a service even exists on your host, this is probably trickier in the home-manager scenario.

You can’t really rely on the user having a canonical ssh key either, since it is typically accessed via the ssh auth socket, and that can in turn call into gpg and do all kinds of fanciful things.

There’s probably a missing feature here, if you can suggest a good workflow I’m sure upstream would be interested.

Not sure what the best solution for this is, maybe this would call into the freedesktop secret service or something. I think the Linux desktop lacks a good way to protect user data in general, which by the way is yet another thing Poeterring seems to want to solve.

Right? I’m not sure if it supports user units yet, but I think about switching to it every once in a while.

Afaict it became available just after sops-nix & co started springing up, so it missed the window during which NixOS desperately needed a good secret management interface, and by then everybody had already found something.

I think it has the potential to be much better than either agenix or sops-nix, but:

  1. Most NixOS modules already have a passwordFile option, so their systemd units don’t use LoadCredential, which makes it more cumbersome to use than it should be on a systemd-focused distro. This also means you now need to modify every systemd service by hand if you want to use it instead.
  2. It kind of needs a good nix module to help deploy the encrypted keys to the host. Wouldn’t be very complicated, but someone needs to work out a nice workflow and publish something to make it easy to use.

Not the first time I’ve thought about starting a project to help with these things, but there are so many things I still want to do :wink:

1 Like

Side note: Just discovered homeage while searching around, looks like it might be worth a try as well, since it specifically targets Home Manager.

1 Like

I am trying the same thing , but -
Here is one approach, at least I need it occassionally
https://git.sr.ht/~bwolf/dotfiles
( read summary)
you can write secrets.yaml on your own , like secrets.nix (?), else
and if I guess this should be related to a way related to parse secrets.nix to secrets.yaml ( although I am working on it)

2c

Hi,
I just recently managed to hop on the sops-nix wagon, here’s my config

After 8d of reading and asking the community around, seems that sops-nix Darwin support is in its early stages however it’s enough to kickoff a basic secret management setup.

The learning curve was a bit steep since I’m an early Nix adopter (nearly 2 months yay!)

I also delved into agenix however noticed that Darwin support is lacking, that’s why I didn’t go with it