Managing secrets in Nix using Agenix
Posted on Fri 25 October 2024 in nix
If tomorrow my laptop were to go in flames, I would lose two things. First I would lose my data, and this is what backups are for. The second thing I would lose is my configuration (software installed, network setup, …). Nix offers a way to perserve this (assuming the configuration is replicated externally like GitHub).
But storing one’s configuration is Nix comes with challenges. One of those challenges is how to handle secrets. Some parts of the configuration is tied to secrets : passwords, keys, API tokens, … . The question is how to manage this. Usually it requires some form of cryptography.
How I store my nix configuration¶
Before I explain Agenix, I think it is worth to explain how I manage my Nix configuration on the various machines I have installed Nix. This is because Agenix integrates to my nix configuration, and Agenix makes more sense in context.
There is the file layout of my Nix configuration over at (GitHub)[https://github.com/mparusinski/nix-config/]
.
├── build.sh
├── flake.lock
├── flake.nix
├── modules
│ ├── nixos
│ │ ├── users.nix
│ │ ├── ...
│ │ └── gc.nix
│ └── home-manager
│ ├── git.nix
│ ├── ...
│ └── zsh.nix
└── hosts
├── dell-precision-7530
│ ├── configuration.nix
│ ├── hardware-configuration.nix
│ └── home.nix
└── wsl1
├── configuration.nix
└── home.nix
(The layout is inspired from …) TODO: Find source
There are two parts each divided into two subparts :
* The build system build on top of Nix and Flakes
* flake.nix
and flake.lock
describe how to build a nix configuration on a given system.
* build.sh
which launches the appropriate Nix build command for the underlying system.
* Nix modules which defines the system configuration
* modules
defines reusable configuration code across various systems
* hosts
defines configuration for given systems.
Naively secrets (passwords, keys, API tokens, …) would be stored in plain text somewhere in the files mentionned here.
Agenix explained¶
To include secrets safely in a Git repository storing configuration there are multiple options. A common choice to externalize secrets. Essentially storing them not in Git. That could be simple files one has to place manually, key vaults, etc. But in that case you can’t simply replicate configuration unless you maintain those secrets. Another choice is to encrypt the secrets before hand, and, it has to be said, making sure the encryption key does not end up in Git.
Agenix offers a way to include those secrets encrypted in Git. Furthermore Agenix is aware of actors involved. By actors I mean the repository maintainer, i.e. you, the systems that will have to decrypt the secrets, …
Agenix is build on top of age which is an encryption tool.
Its particularity is Age encrypted secrets can have multiple recipients and can use SSH
asymetric encryption (a key for encryption, a key for decryption). Agenix uses age
to make
the secret decryptable by authorized actors : you and the systems (which have their own
SSH keys) that need to access the secrets and no one else.
To be used Agenix needs to be installed to encrypt secrets but you also need to specify the nix configuration to setup agenix (so it can manage secrets) which amount of having it installed, the secrets specified and when to use them.
Managing secrets¶
The first step is to install Nix. In my case I am using flakes so I followed the steps from install guide for flakes.
So more details about using flakes for your NixOS configuration see this link
If you want to pass agenix to your configuration module file (usually named configuration.nix
)
you need to specify the specialArgs = { inherit inputs; }
option when calling
nixpkgs.lib.nixosSystem
. To check if agenix is correctly installed run agenix -h
.
Now that agenix is installed the next step is to setup secrets. Inside your nix configuration folder do the following
mkdir secrets
cd secrets
touch secrets.nix
This means that the secrets.nix
file will be version controlled. And start
editing the secrets.nix
file.
let
# SSH public keys of the users who will be adding secrets through agenix
john = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJCizmOo5KevfHd6pqwxVjgvVYWv4Az5TbAclvuhF2AC";
users = [ john ];
# SSH public keys of the systems who will need to access the secrets
webserver = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDA1+TbC/tXsVAKUjSzipoC0ibOgSWuNvzVdb8Xxwi0T";
systems = [ webserver ];
in
{
# Specify for each secret who has access to it
"apikey.age".publicKeys = users ++ systems;
}
Once this is done you can create the secret files. In the above example that would
agenix -e apikey.age
. This file contains the encrypted secret to be used.
Once you have done you will need to specify the secrets twice in the configuration nix files: once to declare the secret with the location of the age encrypted file, the second time to link the secret to some configuration item.
```nix { # configuration.nix … age.secrets.apikey.file = relativepathto/secrets/apikey.age; … # some service requiring the above secret services.special.config.adminFile = config.age.secrets.apikey.path; }
Limitations¶
From my experience sometimes Agenix does not always work smoothly with Nix. For instance in one case the nix build system would invalidate a prometheus configuration because the Agenix path would not exist yet. This had to with the prometheus Nix module validating the generate configuration yaml (see here).