ryantm / agenix

age-encrypted secrets for NixOS and Home manager
https://matrix.to/#/#agenix:nixos.org
Creative Commons Zero v1.0 Universal
1.33k stars 106 forks source link

Implement a fail condition for a scenario where the user does not have a key to decrypt the secret. #262

Open Kreyren opened 2 weeks ago

Kreyren commented 2 weeks ago

A lot of repositories state caution alike:

image from https://github.com/NotAShelf/nyx?tab=readme-ov-file#disclaimer

As currently (r)agenix will just silently fail which results in a blank data parsed to the option.

This complicates some usecases:

  1. if you have a public Infrastructure As Code Management that uses (r)agenix and want to give the visitors the ability to easily deploy your configuration on their hardware or in the VM to see how it works.
  2. In case you want to report a bug and want to give the upstream a VM image where they can reproduce the behavior
  3. Testing the configuration in the VM yourself prior to deployment on a baremetal

As e.g. with disko you currently need to make a script to decrypt all relevant secrets and then parse them to disko to make an image (https://github.com/nix-community/disko/blob/master/docs/disko-images.md) that then you can load in e.g. QEMU.

#!/usr/bin/env sh

nix build -v '.#nixosConfigurations.mracek.config.system.build.diskoImagesScript'

exit 23 # Bruh

# To be managed..
installing the boot loader...
[agenix] creating new generation in /run/agenix.d/1
[agenix] decrypting secrets...
[agenix] WARNING: config.age.identityPaths entry /nix/persist/system/etc/ssh/ssh_host_ed25519_key not present!
decrypting '/nix/store/jn835vbm9rj6km29k3badx5088fbw9cg-homeBaseKreyren-WiFi-PSK.age' to '/run/agenix.d/1/home-wifi-psk'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/run/agenix.d/1/home-wifi-psk.tmp': No such file or directory
mv: cannot stat '/run/agenix.d/1/home-wifi-psk.tmp': No such file or directory
decrypting '/nix/store/npxj471dlz831n2ihiz6i8fx604sg5xi-disks-password.age' to '/run/agenix.d/1/mracek-disks-password'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/run/agenix.d/1/mracek-disks-password.tmp': No such file or directory
mv: cannot stat '/run/agenix.d/1/mracek-disks-password.tmp': No such file or directory
decrypting '/nix/store/ipgjq1wlf6z141jbrrsdyw8hp8biqmy0-mracek-gitea-onion.age' to '/var/lib/tor/mracek-gitea-onion.conf'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/var/lib/tor/mracek-gitea-onion.conf.tmp': No such file or directory
mv: cannot stat '/var/lib/tor/mracek-gitea-onion.conf.tmp': No such file or directory
decrypting '/nix/store/a8vpvx0mlgbjqfbpbrd736fdz3v6sb5n-mracek-monero-onion.age' to '/var/lib/tor/mracek-monero-onion.conf'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/var/lib/tor/mracek-monero-onion.conf.tmp': No such file or directory
mv: cannot stat '/var/lib/tor/mracek-monero-onion.conf.tmp': No such file or directory
decrypting '/nix/store/0c9zka5q1mrq1hkl89hxds08hq3y8bjd-mracek-murmur-onion.age' to '/var/lib/tor/mracek-murmur-onion.conf'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/var/lib/tor/mracek-murmur-onion.conf.tmp': No such file or directory
mv: cannot stat '/var/lib/tor/mracek-murmur-onion.conf.tmp': No such file or directory
decrypting '/nix/store/fhk98p7zg020xpax11iqvp20ypfqwq2a-mracek-navidrome-onion.age' to '/var/lib/tor/mracek-navidrome-onion.conf'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/var/lib/tor/mracek-navidrome-onion.conf.tmp': No such file or directory
mv: cannot stat '/var/lib/tor/mracek-navidrome-onion.conf.tmp': No such file or directory
decrypting '/nix/store/0ljnxahfc1jsvbx0p3g956qnpkmdi1w6-mracek-onion.age' to '/var/lib/tor/mracek-onion.conf'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/var/lib/tor/mracek-onion.conf.tmp': No such file or directory
mv: cannot stat '/var/lib/tor/mracek-onion.conf.tmp': No such file or directory
decrypting '/nix/store/fvyrib2crxz0d9mwhbv8yz15zqzmn9n5-mracek-vikunja-onion.age' to '/var/lib/tor/mracek-vikunja-onion.conf'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/var/lib/tor/mracek-vikunja-onion.conf.tmp': No such file or directory
mv: cannot stat '/var/lib/tor/mracek-vikunja-onion.conf.tmp': No such file or directory
decrypting '/nix/store/7x1bhaybz6zcqmmykwzxhz315aal94fw-pelagus-onion.age' to '/var/lib/tor/pelagus-onion.conf'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/var/lib/tor/pelagus-onion.conf.tmp': No such file or directory
mv: cannot stat '/var/lib/tor/pelagus-onion.conf.tmp': No such file or directory
decrypting '/nix/store/sgbsjm6684alapajh7nkb9p08mnivn6q-sinnenfreude-onion.age' to '/var/lib/tor/sinnenfreude-onion.conf'...
[agenix] WARNING: no readable identities found!
age: error: no identity matched any of the recipients
age: report unexpected or unhelpful errors at https://filippo.io/age/report
chmod: cannot access '/var/lib/tor/sinnenfreude-onion.conf.tmp': No such file or directory
mv: cannot stat '/var/lib/tor/sinnenfreude-onion.conf.tmp': No such file or directory
[agenix] symlinking new secrets to /run/agenix (generation 1)...
Activation script snippet 'agenixInstall' failed (1)

# TODO(Krey): Add `--pre-format-files <src> <dst>` for all the failed secrets above..
eval "sudo $(nix build -v '.#nixosConfigurations.mracek.config.system.build.diskoImagesScript' --print-out-paths) --build-memory 2048 --pre-format-files /run/agenix/mracek-disks-password /run/agenix/mracek-disks-password"

Proposal

Make the attribute to return false when e.g. config.age.secrets.<name>.path is called that (r)agenix is unable to decrypt so that the or operator can be used to supply an alternative secret:

# FIXME-QA(Krey): This should output a huge warning in case the `or` is ever triggered to warn the deployment manager
passwordFile = (config.age.secrets.mracek-disks-password.path or (pkgs.writeText "mracek-disks-password" "000000"));

Alternatively adding an attribute to age.secrets.<name> with an option to declare a fail condition that will trigger in case (r)agenix is unable to decrypt the secret:

age.secrets.mracek-onion-hostname = {
  file = ../secrets/mracek-onion-hostname.age;

  owner = "tor";
  group = "tor";
  mode = "0400"; # Only read for the user

  path = "${config.services.tor.settings.DataDir}/mracek-onion-hostname.conf";

  symlink = false;

  # Relevant option: run this in case (r)agenix is unable to decrypt this secret
  failed = "...";
};
jacekszymanski commented 2 weeks ago

The problem here is that agenix doesn't decrypt at build time, but at activation time, and it doesn't need any keys for a typical configuration to successfully build. It is impossible to make the attribute return false and use the or keyword in this case. To make things worse, secrets are decrypted quite early in a typical use case like nixos boot, so there's not much that can probably be done. Perhaps log failures somewhere and, when system activation ends, mail the log somewhere. OTOH not every use case is typical and sometimes probably such an option would make a bigger difference (EDIT: having thought about this some more, I now believe that, apart from a custom notification, nothing more should be done, and especially not substituting a failed decryption with some "default").

Kreyren commented 2 weeks ago

The problem here is that agenix doesn't decrypt at build time, but at activation time, and it doesn't need any keys for a typical configuration to successfully build. It is impossible to make the attribute return false and use the or keyword in this case. To make things worse, secrets are decrypted quite early in a typical use case like nixos boot, so there's not much that can probably be done. Perhaps log failures somewhere and, when system activation ends, mail the log somewhere. OTOH not every use case is typical and sometimes probably such an option would make a bigger difference. -- @jacekszymanski (https://github.com/ryantm/agenix/issues/262#issuecomment-2173222595)

Can't that be managed by making agenix to generate a service that checks if the file was decrypted in the agenix directory and if not to place an alternative file on it's place?

jacekszymanski commented 2 weeks ago

It would technically be possible to make agenix put some "ersatz" in case of a failed decryption, but it would be anything from merely pointless to outright dangerous: if you can use this "ersatz" file without encryption then just do it, and you don't need agenix; and if you can't, you also don't want to do this at all, it could e.g. start a service with credentials stored in the world-readable Nix store. So while it could be done, it also most definitely shouldn't.

I think that the most that can be done without defeating the very purpose of agenix is store attribute names/paths of failed decryption attempts and then, after the activation, somehow notify the operator that these attempts failed. But this last step should probably be done outside of agenix as this notification might, depending on the concrete use case, be an e-mail, desktop alert, IM message or anything one can imagine.

jacekszymanski commented 2 weeks ago

NB, if you don't care about the secret being accessible in the Nix store, but just don't want to publish it to GitHub or similar, then you don't want agenix, but git-agecrypt; with that you could also check, at build time, whether the secret had been decrypted, and if not, fail the build.