Agenix: Difference between revisions

Dbrgn (talk | contribs)
Fix comment
Tags: Mobile edit Mobile web edit Visual edit
Layer-09 (talk | contribs)
Adherence to the manual
Line 1: Line 1:
[https://github.com/ryantm/agenix agenix] is a commandline tool for managing secrets in your Nix configuration, encrypted with your existing SSH keys. The project also includes the NixOS module age for adding encrypted secrets into the Nix store and decrypting them.
<strong>[https://github.com/ryantm/agenix Agenix]</strong><ref name="agenix">Ryan Mulligan and contributors, "agenix", GitHub Repository, Accessed October 2025. https://github.com/ryantm/agenix</ref> is a command-line tool for managing secrets in Nix configurations. It relies on existing SSH key pairs and integrates with the [https://age-encryption.org/ age] encryption tool to protect secrets while they reside in the Nix store and during system activation.<ref name="age">Filippo Valsorda and collaborators, "age", Official Website, Accessed October 2025. https://age-encryption.org/</ref><ref name="agenix-readme">Ryan Mulligan and contributors, "agenix README", GitHub Repository, Accessed October 2025. https://github.com/ryantm/agenix/blob/main/README.md</ref>
 
== Features ==
 
* <strong>SSH-based encryption workflow:</strong> The `agenix` CLI encrypts raw secret files with user and host SSH public keys, allowing decryption wherever the matching private keys are present.<ref name="agenix-readme" />
* <strong>Reproducible storage model:</strong> Encrypted `.age` files can live in version control, enter the Nix store, and are decrypted during system activation to paths such as <code>/run/agenix</code>.<ref name="agenix-readme" />
* <strong>Declarative module support:</strong> NixOS and Home Manager modules ship with the project, so secrets can be imported, decrypted, and exposed declaratively alongside other configuration.<ref name="agenix-readme" />
* <strong>Minimal dependency footprint:</strong> Agenix depends on the `age` tool rather than GnuPG and intentionally omits templating features, keeping the code base small and auditable.<ref name="agenix-readme" /><ref name="comparison">NixOS Wiki Community, "Comparison of secret managing schemes", NixOS Wiki, Accessed October 2025. https://wiki.nixos.org/wiki/Comparison_of_secret_managing_schemes</ref>


== Installation ==
== Installation ==


The following example describes an installation via [[Flakes]]. For further installation methods see the [https://github.com/ryantm/agenix upstream documentation].
The following example describes installing [https://github.com/ryantm/agenix Agenix] with [[Flakes]].<ref name="agenix" /> For additional installation methods, consult the README.<ref name="agenix-readme" />


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
Line 23: Line 30:
</syntaxhighlight>
</syntaxhighlight>


Change <code>yourhostname</code> to your actual hostname and <code>x86_64-linux</code> to your system architecture.
Replace <code>yourhostname</code> with the actual host name and <code>x86_64-linux</code> with the relevant system architecture.


After that installing the agenix client application can be achieved like this
After adding the input, install the <code>agenix</code> client application with:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
Line 37: Line 44:
== Configuration ==
== Configuration ==


=== Choose a Public/Private Key ===
=== Choose a public/private key ===


First, we have to decide which [[SSH public key authentication|SSH public key]] to use to encrypt the secrets. (The private key will be used to decrypt the secrets when loading the NixOS configuration.)
Decide which [[SSH public key authentication|SSH public key]] should encrypt the secrets. The corresponding private key decrypts the secrets when rebuilding the system.


Assuming that you have [[SSH]] already installed on your NixOS server (with i.e. <code>services.openssh.enable = true;</code>), you will already have two different SSH keypairs that are intended to be used by the system itself, located at:
If you deploy the configuration as the root user, the host key pairs generated by [[SSH]] are already available at:


* <code>/etc/ssh/ssh_host_rsa_key</code> / <code>/etc/ssh/ssh_host_rsa_key.pub</code>
* <code>/etc/ssh/ssh_host_rsa_key</code> / <code>/etc/ssh/ssh_host_rsa_key.pub</code>
* <code>/etc/ssh/ssh_host_ed25519_key</code> / <code>/etc/ssh/ssh_host_ed25519_key.pub</code>
* <code>/etc/ssh/ssh_host_ed25519_key</code> / <code>/etc/ssh/ssh_host_ed25519_key.pub</code>


If you load your NixOS config using the root user, then you can use these public keys to encrypt your secrets.
For deployments triggered by another user, create a dedicated key pair with <code>ssh-keygen</code>. Such keys typically reside at:
 
However, if you load your NixOS config using some other user, then you will have to use <code>ssh-keygen</code> to generate a keypair for that user, which typically lives in:


* <code>~/.ssh/id_rsa</code> / <code>~/.ssh/id_rsa.pub</code>
* <code>~/.ssh/id_rsa</code> / <code>~/.ssh/id_rsa.pub</code>
* <code>~/.ssh/id_ed25519</code> / <code>~/.ssh/id_ed25519.pub</code>
* <code>~/.ssh/id_ed25519</code> / <code>~/.ssh/id_ed25519.pub</code>


For more information, see [[SSH_public_key_authentication|the SSH public key authentication page]].
Refer to [[SSH public key authentication]] for more information.


=== Create the Secrets ===
=== Create the secrets ===


Next, create a directory where secrets are going to be stored. In this example we are creating the directory <code>secrets</code> inside the NixOS system configuration path <code>/etc/nixos</code>
Create a directory that stores encrypted secrets. For example:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 63: Line 68:
</syntaxhighlight>
</syntaxhighlight>


Inside the secrets directory we create a <code>secrets.nix</code> file which will be used by the agenix client as a rule file to encrypt secrets for specific users and parts of the system. The following example configures access to secrets stored in <code>secret1.age</code> for the SSH public keys of <code>user1</code> and <code>system1</code>.
Inside the secrets directory, create a <code>secrets.nix</code> file that defines the encryption rules. The following example gives <code>user1</code> and <code>system1</code> access to <code>secret1.age</code>:


{{file|/etc/nixos/secrets/secrets.nix|nix|<nowiki>
{{file|/etc/nixos/secrets/secrets.nix|nix|<nowiki>
Line 80: Line 85:
== Usage ==
== Usage ==


Creating a secret file, which contents will be encrypted
Create a secret file and encrypt it by running:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 87: Line 92:
</syntaxhighlight>
</syntaxhighlight>


The agenix command will open your default terminal editor. Write in your secret, for example <code>password123</code>.
The <code>agenix</code> command opens the default terminal editor. Enter the secret (for example, <code>password123</code>) and save the file. The <code>secret1.age</code> filename is defined in <code>secrets.nix</code>, allowing <code>agenix</code> to select the correct SSH keys.<ref name="agenix-readme" />


The filename <code>secret1.age</code> is specified above in the agenix <code>secrets.nix</code> configuration. So agenix will know which keys to use for a specific user or system.
To reference the secret inside a NixOS module, use a configuration similar to the following example:
 
To use and reference the secret inside your Nix configuration, an example would look like this


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
Line 107: Line 110:
</syntaxhighlight>
</syntaxhighlight>


Here, the service [[Nextcloud]] requires a password for the administrator account. In this case, the password is stored in an age-encrypted file, so no plaintext passwords will be copied into your world-readable Nix-store. We configure <code>owner</code> and <code>group</code> names to <code>nextcloud</code> so that the webservice has the permissions to read the password file.
In this case, the [[Nextcloud]] service reads its administrator password from the age-encrypted file, preventing the secret from appearing in the world-readable Nix store.<ref name="agenix-readme" />


Secrets can be also deployed as file with specific permissions to a target path. In this example the secret is sourced to <code>/home/myuser/.netrc</code> and permissions are set that only <code>myuser</code> is able to read and write the file.
Secrets can also be deployed as files with specific permissions:<ref name="agenix-readme" />


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
Line 125: Line 128:
== Tips and tricks ==
== Tips and tricks ==


=== Replace inplace strings with secrets ===
=== Replace in-place strings with secrets ===


Considering that there still might be some modules which doesn't support reading secrets from a file, you could provide a placeholder string instead of a clear-text password and replace this placeholder with the secret provided by Agenix.
Some modules do not yet support reading secrets from a file. Provide a placeholder string and replace it during activation.
 
In the following example, the Dex module creates the config file <code>/run/dex/config.yaml</code> containing the placeholder string <code>@dex-user-password@</code>. The activation script will read the Agenix secret from <code>config.age.secret.dex-user-password.path</code> and replace the placeholder string with the actual secret.


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
Line 139: Line 140:
</syntaxhighlight>
</syntaxhighlight>


=== Access secrets inside container ===
=== Access secrets inside a container ===


Using the option <code>bindMounts</code> for an example container named <code>mycontainer</code> will provide the secret file inside the container as <code>/run/agenix/mysecret</code>:
Expose secrets to a container by binding the decrypted file into the container filesystem:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
Line 147: Line 148:
</syntaxhighlight>
</syntaxhighlight>


Another option would be to to use the agenix-module in the nixos-container. This also allows to set the secret-owner to the users inside the container. But it is also necessary to provide the ssh-private-key to the container in order for agenix to decrypt the secret (or generate a own for the container).
Another option is to import <code>agenix.nixosModules.default</code> directly inside the container and supply the required private key:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{ agenix, ... }: {
{ agenix, ... }: {
   containers."mycontainer" = {
   containers."mycontainer" = {
     # pass the private key to the container for agenix to decrypt the secret
     # pass the private key to the container for agenix to decrypt the secret
     bindMounts."/etc/ssh/ssh_host_ed25519_key".isReadOnly = true;
     bindMounts."/etc/ssh/ssh_host_ed25519_key".isReadOnly = true;


     config = { config, lib, pkgs, ... }: {
     config = { config, lib, pkgs, ... }: {
      imports = [ agenix.nixosModules.default ];


      imports = [ agenix.nixosModules.default ]; # import agenix-module into the nixos-container
       age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
 
       age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; # needed when OpenSSH is not set up in container


      # import the secret
       age.secrets."mysecret" = {
       age.secrets."mysecret" = {
         file = ../secrets/mysecret.age;
         file = ../secrets/mysecret.age;
         owner = "myuser";
         owner = "myuser";
       };
       };
      # use the secret like you normally would with config.age.secrets."mysecret".path
     };
     };
   };
   };
Line 175: Line 170:
</syntaxhighlight>
</syntaxhighlight>


=== Using secrets in initrd ===
=== Use secrets in the initrd ===


Unfortunately this doesn't work because Agenix sets up secrets during system activation stage but initrd is being built before that. As [https://github.com/ryantm/agenix/issues/193 a workaround] we could create the secret as a static file outside of <code>/run/agenix</code> and reference the secret at <code>/etc/initrd-hostkey</code>.
Agenix provisions secrets during system activation, so they are unavailable while building the initrd. As a workaround, create the secret as a static file outside <code>/run/agenix</code> and reference it from <code>/etc/initrd-hostkey</code>.


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
Line 189: Line 184:
</syntaxhighlight>
</syntaxhighlight>


For this workaround you'll have to rebuild twice and reference the secret <code>/etc/initrd-hostkey</code> only after the file is created.
Rebuild the system twice and reference <code>/etc/initrd-hostkey</code> only after the file exists.


=== Agenix with Impermanence ===
=== Agenix with Impermanence ===
If your system is configured to be [[Impermanence|impermanent]], then it's possible the system's ssh keys won't yet be available during boot to decrypt secrets. The solution is to manually set <code>age.identityPaths</code> to the persistent paths of your keys.
<syntaxhighlight lang="nix">


# Direct path to persistent location of system ssh keys
Systems that use [[Impermanence]] may regenerate host keys on each boot. Point <code>age.identityPaths</code> to the persistent locations of those keys:


<syntaxhighlight lang="nix">
age.identityPaths = [
age.identityPaths = [
   "/persist/etc/ssh/ssh_host_ed25519_key"
   "/persist/etc/ssh/ssh_host_ed25519_key"
   "/persist/etc/ssh/ssh_host_rsa_key"
   "/persist/etc/ssh/ssh_host_rsa_key"
];
];
</syntaxhighlight>
</syntaxhighlight>


== See also ==
== See also ==
* [[Comparison of secret managing schemes]]
 
* [[Comparison of secret managing schemes]] – Overview of alternative secret management approaches
* [https://age-encryption.org/ age] – Encryption tool that underpins Agenix secrets<ref name="age" />
* [[Home Manager]] – Declarative per-user configuration that can consume Agenix-managed secrets
 
== References ==
 
<references />


[[Category:Applications]]
[[Category:Applications]]
[[Category:Security]]
[[Category:Security]]