Bootstrap fresh install using agenix for secrets management

I’m trying to figure out how to properly use agenix for users.users.<name>.passwordFile on a fresh install with flakes. I ran into a chicken-and-egg problem with the password and the ssh keys when I tried to add this to the draft config I’ve been learning in.

I’ve pruned and sanitized my draft config and posted it here on github. The main branch uses passwordFile without agenix. It’s not as fast or polished as nixos-up, but it has LUKS, and I’m experimenting with darling erasure.

My goal is to get to a (fairly) minimal example, without a lot of wrappers, on how to bootstrap a new system using agenix for secret management, while keeping the LUKS and darling erasure components. Then I may revise to use ragenix, and compare with sops-nix.

I’ve come up with one potential solution while pruning the example to share, so I’ll start adding agenix in another branch, step-by-step.

In the meantime, if anyone can point me to a working example, or has other advice on how to improve the config, please let me know.

2 Likes

My most recent effort at adding secrets with agenix is in the agenix branch now.

  • ed8550c I added agenix as an input, and included it as an installed package in the live iso and the installed system

  • a4ef773 On my development machine, I followed the agenix tutorial and:

    • generated two new ed25519 keys without passphrases – one for the user and the other for the system
      ssh-keygen -t ed25519 -f /tmp/.ssh/nix/flynn/id_ed25519 -C ""
      ssh-keygen -t ed25519 -f /tmp/.ssh/nix/encom/id_ed25519 -C ""
      
    • added secrets.nix with those keys specified to decode hashedPassword.age
    • edited the secret file using:
      nix run github:ryantm/agenix -- -e "hashedPassword.age" -i /tmp/.ssh/nix/flynn/id_ed25519
      
      and pasted in the hashed password output of:
      mkpasswd -m sha512crypt | xclip -sel clip
      
    • added both secrets.nix and hashedPassword.age to the repo
    • added the secret to user/flynn/default.nix
    • changed users.users.flynn.passwordFile to config.age.secrets.hashedPassword.path instead of the earlier out-of-band hashed password file
  • Then I boot into the live iso, connect the network, and copy the keys from my development machine to /root/.ssh on the live iso.

    • I also tried by copying the keys to the nixos user of the iso, but don’t expect that to work because nixos-install needs to be run with sudo or as root.

Unfortunately, agenix gets an error from rage during nixos-install, which prevents decryption, so /run/agenix/hashedPassword does not exist and the user flynn does not have a password set in the resulting system.


complete error information:

[nixos@nixos:~]$ sudo nixos-install --flake github:bluesquall/tabula-rasa/agenix#encom --no-root-password
copying channel...
building the flake in github:bluesquall/tabula-rasa/8f0a004a1abf7e7e94c7df0429fe99794542cc78...
installing the boot loader...
[agenix] symlinking new secrets to /run/agenix (generation 1)...
[agenix] decrypting root secrets...
decrypting '/nix/store/mk50w869fvji6x7qh0mff4z469xyx7hf-hashedPassword.age' to '/run/agenix.d/1/hashedPassword'...
Error: No such file or directory (os error 2)

[ Did rage not do what you expected? Could an error be more useful? ]
[ Tell us: https://str4d.xyz/rage/report                            ]
chmod: cannot access '/run/agenix.d/1/hashedPassword.tmp': No such file or directory
chown: cannot access '/run/agenix.d/1/hashedPassword.tmp': No such file or directory
mv: cannot stat '/run/agenix.d/1/hashedPassword.tmp': No such file or directory
Activation script snippet 'agenixRoot' failed (1)
warning: password file ‘/run/agenix/hashedPassword’ does not exist
[agenix] decrypting non-root secrets...
setting up /etc...
Copied "/nix/store/cfhpzlarbhfw3scj91dcz5ai04ayfzik-systemd-249.7/lib/systemd/boot/efi/systemd-bootx64.efi" to "/boot/EFI/systemd/systemd-bootx64.efi".
Copied "/nix/store/cfhpzlarbhfw3scj91dcz5ai04ayfzik-systemd-249.7/lib/systemd/boot/efi/systemd-bootx64.efi" to "/boot/EFI/BOOT/BOOTX64.EFI".
Random seed file /boot/loader/random-seed successfully written (512 bytes).
Not installing system token, since we are running in a virtualized environment.
Created EFI boot entry "Linux Boot Manager".
installation finished!

cool tricks:

  • Instead of cutting onto the clipboard from another terminal, if EDITOR=nvim, you can generate the password using :r! mkpasswd -msha512crypt <passwd> and wipe that entry from the vim command history with :call histdel(":", "mkpasswd").
2 Likes

Probably not surprising, but the branch using ragenix runs into the same error.

Along a different path, it seems like I got sops-nix to work in this branch. There was a hint that perhaps the keys need to be on the root subvolume due to boot & build ordering. I’m not sure whether that applies to (r)agenix as well, but I’ll look into it when I get time to circle back.

Revising the sops-nix branch to enable darling erasure was a bit complicated, but eventually very simple. The exercise actually makes me question if there’s any system state that I would want to persist through boots that isn’t:

  • required for bootstrapping in the first place (or almost required)
  • a secret that sops-nix or agenix should track
  • a non-secret that can be plaintext in the config

(Note that I don’t consider /home to be system state, so it is on a different subvolume that won’t be wiped by darling erasure.)

It seems impossible to use the same flake for the initial build and for subsequent rebuilds, unless you set everything you want to persist from the start. This effectively makes it necessary information for bootstrapping.

Since I couldn’t make sops-nix happy with the private keys on a separate subvolume, I had to put them on the root subvolume & take a snapshot of it with the minimal files required to bootstrap from. This makes the root-blank snapshot and the persist subvolume unnecessary.

I’ll circle back to the (r)agenix approach with this perspective shift after I’ve experimented a bit more with the working sops-nix branch.

This was actually due to a problem with re-keying the secrets file when I switched over from bootstrapping with a user’s age key to bootstrapping with a pre-generated ssh-ed25519 host key.

I assumed that adding the age public key derived from the pre-generated ssh-ed25519 host key to .sops.yaml and then editing the secret with sops -e path/to/secrets.yaml would encode the secret for the new public key, but it turns out that you need sops updatekeys path/to/secrets.yaml to re-key.

Once that was fixed, using sops.age.sshKeyPaths = [ "/persist/etc/ssh/ssh_host_ed25519_key" ]; worked just fine.

The perspective shift still applies, though, so I’ll continue to evaluate my alternate approach that uses a bootstrapped snapshot of root rather than symlinking from a separate subvolume.

Looks like both agenix and ragenix now work with the host key. I’ll revisit sometime later with the user key.

In the meantime, the live system iso includes agenix as a program so that you can re-key the secrets if you have the user key.

1 Like