Jump to content

Agenix: Difference between revisions

From NixOS Wiki
Layer-09 (talk | contribs)
Adherence to the manual
Layer-09 (talk | contribs)
m Translate tags
 
Line 1: Line 1:
<languages/>
<translate>
<!--T:1-->
<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>
<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 ==
== Features == <!--T:2-->


<!--T:3-->
* <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>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>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" />
Line 8: Line 13:
* <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>
* <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 == <!--T:4-->


<!--T:5-->
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" />
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" />
 
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
{
Line 29: Line 35:
}
}
</syntaxhighlight>
</syntaxhighlight>
 
<translate>
<!--T:6-->
Replace <code>yourhostname</code> with the actual host name and <code>x86_64-linux</code> with the relevant system architecture.
Replace <code>yourhostname</code> with the actual host name and <code>x86_64-linux</code> with the relevant system architecture.


<!--T:7-->
After adding the input, install the <code>agenix</code> client application with:
After adding the input, install the <code>agenix</code> client application with:
 
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{ config, pkgs, lib, inputs, ... }:{
{ config, pkgs, lib, inputs, ... }:{
Line 41: Line 49:
}
}
</syntaxhighlight>
</syntaxhighlight>
<translate>
== Configuration == <!--T:8-->


== Configuration ==
=== Choose a public/private key === <!--T:9-->
 
=== Choose a public/private key ===


<!--T:10-->
Decide which [[SSH public key authentication|SSH public key]] should encrypt the secrets. The corresponding private key decrypts the secrets when rebuilding the system.
Decide which [[SSH public key authentication|SSH public key]] should encrypt the secrets. The corresponding private key decrypts the secrets when rebuilding the system.


<!--T:11-->
If you deploy the configuration as the root user, the host key pairs generated by [[SSH]] are already available at:
If you deploy the configuration as the root user, the host key pairs generated by [[SSH]] are already available at:


<!--T:12-->
* <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>


<!--T:13-->
For deployments triggered by another user, create a dedicated key pair with <code>ssh-keygen</code>. Such keys typically reside at:
For deployments triggered by another user, create a dedicated key pair with <code>ssh-keygen</code>. Such keys typically reside at:


<!--T:14-->
* <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>


<!--T:15-->
Refer to [[SSH public key authentication]] for more information.
Refer to [[SSH public key authentication]] for more information.


=== Create the secrets ===
=== Create the secrets === <!--T:16-->


<!--T:17-->
Create a directory that stores encrypted secrets. For example:
Create a directory that stores encrypted secrets. For example:
 
</translate>
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# mkdir /etc/nixos/secrets
# mkdir /etc/nixos/secrets
</syntaxhighlight>
</syntaxhighlight>
 
<translate>
<!--T:18-->
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>:
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>:
 
</translate>
{{file|/etc/nixos/secrets/secrets.nix|nix|<nowiki>
{{file|/etc/nixos/secrets/secrets.nix|nix|<nowiki>
let
let
Line 82: Line 98:
}
}
</nowiki>}}
</nowiki>}}
<translate>
== Usage == <!--T:19-->


== Usage ==
<!--T:20-->
 
Create a secret file and encrypt it by running:
Create a secret file and encrypt it by running:
 
</translate>
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
# cd /etc/nixos/secrets
# cd /etc/nixos/secrets
# agenix -e secret1.age
# agenix -e secret1.age
</syntaxhighlight>
</syntaxhighlight>
 
<translate>
<!--T:21-->
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 <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" />


<!--T:22-->
To reference the secret inside a NixOS module, use a configuration similar to the following example:
To reference the secret inside a NixOS module, use a configuration similar to the following example:
 
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
age.secrets.nextcloud = {
age.secrets.nextcloud = {
Line 109: Line 128:
};
};
</syntaxhighlight>
</syntaxhighlight>
 
<translate>
<!--T:23-->
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" />
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" />


<!--T:24-->
Secrets can also be deployed as files with specific permissions:<ref name="agenix-readme" />
Secrets can also be deployed as files with specific permissions:<ref name="agenix-readme" />
 
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
age.secrets = {
age.secrets = {
Line 125: Line 146:
};
};
</syntaxhighlight>
</syntaxhighlight>
<translate>
== Tips and tricks == <!--T:25-->


== Tips and tricks ==
=== Replace in-place strings with secrets === <!--T:26-->
 
=== Replace in-place strings with secrets ===


<!--T:27-->
Some modules do not yet support reading secrets from a file. Provide a placeholder string and replace it during activation.
Some modules do not yet support reading secrets from a file. Provide a placeholder string and replace it during activation.
 
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
system.activationScripts."dex-user-secret" = ''
system.activationScripts."dex-user-secret" = ''
Line 139: Line 161:
'';
'';
</syntaxhighlight>
</syntaxhighlight>
<translate>
=== Access secrets inside a container === <!--T:28-->


=== Access secrets inside a container ===
<!--T:29-->
 
Expose secrets to a container by binding the decrypted file into the container filesystem:
Expose secrets to a container by binding the decrypted file into the container filesystem:
 
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
containers.mycontainer.bindMounts."${config.agenix.secrets.mysecret.path}".isReadOnly = true;
containers.mycontainer.bindMounts."${config.agenix.secrets.mysecret.path}".isReadOnly = true;
</syntaxhighlight>
</syntaxhighlight>
 
<translate>
<!--T:30-->
Another option is to import <code>agenix.nixosModules.default</code> directly inside the container and supply the required private key:
Another option is to import <code>agenix.nixosModules.default</code> directly inside the container and supply the required private key:
 
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{ agenix, ... }: {
{ agenix, ... }: {
Line 169: Line 193:
}
}
</syntaxhighlight>
</syntaxhighlight>
<translate>
=== Use secrets in the initrd === <!--T:31-->


=== Use secrets in the initrd ===
<!--T:32-->
 
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>.
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>.
 
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
age.secrets.hostkey-initrd = {
age.secrets.hostkey-initrd = {
Line 183: Line 208:
boot.initrd.network.ssh.hostKeys = [ "/etc/initrd-hostkey" ];
boot.initrd.network.ssh.hostKeys = [ "/etc/initrd-hostkey" ];
</syntaxhighlight>
</syntaxhighlight>
 
<translate>
<!--T:33-->
Rebuild the system twice and reference <code>/etc/initrd-hostkey</code> only after the file exists.
Rebuild the system twice and reference <code>/etc/initrd-hostkey</code> only after the file exists.


=== Agenix with Impermanence ===
=== Agenix with Impermanence === <!--T:34-->


<!--T:35-->
Systems that use [[Impermanence]] may regenerate host keys on each boot. Point <code>age.identityPaths</code> to the persistent locations of those keys:
Systems that use [[Impermanence]] may regenerate host keys on each boot. Point <code>age.identityPaths</code> to the persistent locations of those keys:
 
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
age.identityPaths = [
age.identityPaths = [
Line 196: Line 223:
];
];
</syntaxhighlight>
</syntaxhighlight>
<translate>
== See also == <!--T:36-->


== See also ==
<!--T:37-->
 
* [[Comparison of secret managing schemes]] – Overview of alternative secret management approaches
* [[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" />
* [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
* [[Home Manager]] – Declarative per-user configuration that can consume Agenix-managed secrets


== References ==
== References == <!--T:38-->


<references />
<references />
Line 209: Line 237:
[[Category:Applications]]
[[Category:Applications]]
[[Category:Security]]
[[Category:Security]]
</translate>

Latest revision as of 05:26, 18 October 2025


Agenix[1] is a command-line tool for managing secrets in Nix configurations. It relies on existing SSH key pairs and integrates with the age encryption tool to protect secrets while they reside in the Nix store and during system activation.[2][3]

Features

  • SSH-based encryption workflow: The `agenix` CLI encrypts raw secret files with user and host SSH public keys, allowing decryption wherever the matching private keys are present.[3]
  • Reproducible storage model: Encrypted `.age` files can live in version control, enter the Nix store, and are decrypted during system activation to paths such as /run/agenix.[3]
  • Declarative module support: NixOS and Home Manager modules ship with the project, so secrets can be imported, decrypted, and exposed declaratively alongside other configuration.[3]
  • Minimal dependency footprint: Agenix depends on the `age` tool rather than GnuPG and intentionally omits templating features, keeping the code base small and auditable.[3][4]

Installation

The following example describes installing Agenix with Flakes.[1] For additional installation methods, consult the README.[3]

{
  inputs.agenix.url = "github:ryantm/agenix";
  # optional, not necessary for the module
  #inputs.agenix.inputs.nixpkgs.follows = "nixpkgs";

  outputs = { self, nixpkgs, agenix }: {
    nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        agenix.nixosModules.default
      ];
    };
  };
}

Replace yourhostname with the actual host name and x86_64-linux with the relevant system architecture.

After adding the input, install the agenix client application with:

{ config, pkgs, lib, inputs, ... }:{
  environment.systemPackages = [
    inputs.agenix.packages."${system}".default
  ];
}

Configuration

Choose a public/private key

Decide which SSH public key should encrypt the secrets. The corresponding private key decrypts the secrets when rebuilding the system.

If you deploy the configuration as the root user, the host key pairs generated by SSH are already available at:

  • /etc/ssh/ssh_host_rsa_key / /etc/ssh/ssh_host_rsa_key.pub
  • /etc/ssh/ssh_host_ed25519_key / /etc/ssh/ssh_host_ed25519_key.pub

For deployments triggered by another user, create a dedicated key pair with ssh-keygen. Such keys typically reside at:

  • ~/.ssh/id_rsa / ~/.ssh/id_rsa.pub
  • ~/.ssh/id_ed25519 / ~/.ssh/id_ed25519.pub

Refer to SSH public key authentication for more information.

Create the secrets

Create a directory that stores encrypted secrets. For example:

# mkdir /etc/nixos/secrets

Inside the secrets directory, create a secrets.nix file that defines the encryption rules. The following example gives user1 and system1 access to secret1.age:

❄︎ /etc/nixos/secrets/secrets.nix
let
  user1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH";
  users = [ user1 ];

  system1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE";
  systems = [ system1 ];
in
{
  "secret1.age".publicKeys = [ user1 system1 ];
}

Usage

Create a secret file and encrypt it by running:

# cd /etc/nixos/secrets
# agenix -e secret1.age

The agenix command opens the default terminal editor. Enter the secret (for example, password123) and save the file. The secret1.age filename is defined in secrets.nix, allowing agenix to select the correct SSH keys.[3]

To reference the secret inside a NixOS module, use a configuration similar to the following example:

age.secrets.nextcloud = {
  file = ./secrets/secret1.age;
  owner = "nextcloud";
  group = "nextcloud";
};
services.nextcloud = {
  enable = true;
  package = pkgs.nextcloud28;
  hostName = "localhost";
  config.adminpassFile = config.age.secrets.nextcloud.path;
};

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.[3]

Secrets can also be deployed as files with specific permissions:[3]

age.secrets = {
  netrc = {
    file = ./secrets/netrc.age;
    path = "/home/myuser/.netrc";
    owner = "myuser";
    group = "users";
    mode = "600";
  };
};

Tips and tricks

Replace in-place strings with secrets

Some modules do not yet support reading secrets from a file. Provide a placeholder string and replace it during activation.

system.activationScripts."dex-user-secret" = ''
  secret=$(cat "${config.age.secrets.dex-user-password.path}")
  configFile=/run/dex/config.yaml
  ${pkgs.gnused}/bin/sed -i "s#@dex-user-password@#$secret#" "$configFile"
'';

Access secrets inside a container

Expose secrets to a container by binding the decrypted file into the container filesystem:

containers.mycontainer.bindMounts."${config.agenix.secrets.mysecret.path}".isReadOnly = true;

Another option is to import agenix.nixosModules.default directly inside the container and supply the required private key:

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

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

      age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];

      age.secrets."mysecret" = {
        file = ../secrets/mysecret.age;
        owner = "myuser";
      };
    };
  };
}

Use secrets in the initrd

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 /run/agenix and reference it from /etc/initrd-hostkey.

age.secrets.hostkey-initrd = {
  file = "${paths.agenix}/hostkey-initrd.age";
  path = "/etc/initrd-hostkey";
  symlink = false;
};

boot.initrd.network.ssh.hostKeys = [ "/etc/initrd-hostkey" ];

Rebuild the system twice and reference /etc/initrd-hostkey only after the file exists.

Agenix with Impermanence

Systems that use Impermanence may regenerate host keys on each boot. Point age.identityPaths to the persistent locations of those keys:

age.identityPaths = [
  "/persist/etc/ssh/ssh_host_ed25519_key"
  "/persist/etc/ssh/ssh_host_rsa_key"
];

See also

References

  1. 1.0 1.1 Ryan Mulligan and contributors, "agenix", GitHub Repository, Accessed October 2025. https://github.com/ryantm/agenix
  2. 2.0 2.1 Filippo Valsorda and collaborators, "age", Official Website, Accessed October 2025. https://age-encryption.org/
  3. 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 Ryan Mulligan and contributors, "agenix README", GitHub Repository, Accessed October 2025. https://github.com/ryantm/agenix/blob/main/README.md
  4. NixOS Wiki Community, "Comparison of secret managing schemes", NixOS Wiki, Accessed October 2025. https://wiki.nixos.org/wiki/Comparison_of_secret_managing_schemes