Encrypting Secrets in NixOS With Agenix
After playing around with NixOS and creating declarative configurations I quickly came across a need to provide encrypted secrets, rather than clear text. This is a requirement to be able to save configurations on GitHub and is recommended anyway, given that clear text secrets are made available on the world readable Nix store.
There are a number of solutions available to encrypt secrets in NixOS, I landed on Agenix, as it's relatively easy to configure and works with my deployment tool of choice - Deploy-rs.
How Agenix Works
Agenix uses pre-existing SSH keys to encrypt secrets using the Age encryption tool. These secrets are then decrypted using the private SSH host key on the target system.
There are a couple of points worth considering when using Agenix:
- Agenix doesn't officially have support for Home Manager - though an alternative is available (I haven't tried this)
- Agenix provides a secret file, rather than a string. This means that if there is no option in an existing module to pass a filepath for a secret, we need to have the service read the clear text file path at runtime
Using Agenix
I'll run through a simple example here, should you want more detail I recommend reading through the Agenix readme.
Agenix Configuration
Create a directory for secrets and secret config - this should be somewhere that makes sense e.g. /etc/nixos/secrets
Next we need to create a secret configuration file secrets.nix
. This tells Agenix which SSH keys to use when encrypting secrets, which secrets are available, and which systems/users should have access to each secret.
mkdir -p /etc/nixos/secrets
vim /etc/nixos/secrets/secrets.nix
The contents of the file should look something like this:
# /etc/nixos/secrets/secrets.nix
let
system = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH";
{
"secretpassword.age".publicKeys = [ system ];
}
Creating a Secret
We are now ready to create out first secret secretpassword
. This next part will differ depending on how you have installed Agenix.
Given I'm using Nix Flakes, I can simply run:
nix run github:ryantm/agenix -- -e secretpassword.age
The above assumes my SSH keys have been added to ~/.ssh/
. If this is not the case we can specify which SSH keys to use:
nix run github:ryantm/agenix -- -e secretpassword.age -i /path/to/ssh/key
Adding Secret to NixOS
Once the above has been completed you can now add secrets to your NixOS configurations like so:
# /etc/nixos/configuration.nix
{
users.users.captainsecure = {
isNormalUser = true;
home = "/home/captainsecure";
passwordFile = config.age.secrets.secretpassword.path;
};
age.secrets.secretpassword.file = ../../secrets/secretpassword.age;
}
Secrets Folder Structure
I discovered an interesting technique for organising Agenix secrets into directories, thanks to Charlotte Van Petegem's dotfiles. I didn't see this explained in the Agenix manual, but it looks like we can nest age secrets within the secrets directory e.g.
# /etc/nixos/secrets
.
├── secrets.nix
├── passwords
│ └── secretpassword.age
When we need to refer to these nested secrets in our Nix configuration, we specify the path to each age file e.g.
# /etc/nixos/configuration.nix
{
users.users.captainsecure = {
isNormalUser = true;
home = "/home/captainsecure";
passwordFile = config.age.secrets."passwords/secretpassword".path;
};
age.secrets."passwords/secretpassword".file = ../../secrets/passwords/secretpassword.age;
}