Agenix: Difference between revisions

imported>Onny
Start adding configuration section
Layer-09 (talk | contribs)
m Translate tags
 
(24 intermediate revisions by 9 users not shown)
Line 1: Line 1:
[https://github.com/ryantm/agenix agenix] is a commandline tool for managing secrets 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.
<languages/>


== Installation ==
<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>


The following example describes an installation via [[Flakes]]. For further installation methods see the [https://github.com/ryantm/agenix upstream documentation].
== 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>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 == <!--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" />
</translate>
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
{
Line 16: Line 29:
       modules = [
       modules = [
         ./configuration.nix
         ./configuration.nix
         agenix.nixosModule
         agenix.nixosModules.default
       ];
       ];
     };
     };
Line 22: 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.


Change <code>yourhostname</code> to your actual hostname and <code>x86_64-linux</code> to your system architecture.
<!--T:7-->
 
After adding the input, install the <code>agenix</code> client application with:
After that installing the agenix client application can be achieved like this
</translate>
 
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{ config, pkgs, lib, inputs, ... }:{
{ config, pkgs, lib, inputs, ... }:{
   environment.systemPackages = [
   environment.systemPackages = [
     inputs.agenix.defaultPackage."${system}"
     inputs.agenix.packages."${system}".default
   ];
   ];
}
}
</syntaxhighlight>
</syntaxhighlight>
<translate>
== Configuration == <!--T:8-->
=== Choose a public/private key === <!--T:9-->
<!--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.
<!--T:11-->
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_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:
<!--T:14-->
* <code>~/.ssh/id_rsa</code> / <code>~/.ssh/id_rsa.pub</code>
* <code>~/.ssh/id_ed25519</code> / <code>~/.ssh/id_ed25519.pub</code>


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


First create a directory where secrets are going to be stored. In this example we're creating the directory <code>secrets</code> inside the NixOS system configuration path <code>/etc/nixos</code>
=== Create the secrets === <!--T:16-->


<!--T:17-->
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>
Inside the secrets directory we create a <code>secrets.nix</code> file which will be used by the agenix client to encrypt secrets for specific users and parts of the system
<!--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>:
</translate>
{{file|/etc/nixos/secrets/secrets.nix|nix|<nowiki>
{{file|/etc/nixos/secrets/secrets.nix|nix|<nowiki>
let
let
   user1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH";
   user1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH";
  user2 = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCoQ9S7V+CufAgwoehnf2TqsJ9LTsu8pUA3FgpS2mdVwcMcTs++8P5sQcXHLtDmNLpWN4k7NQgxaY1oXy5e25x/4VhXaJXWEt3luSw+Phv/PB2+aGLvqCUirsLTAD2r7ieMhd/pcVf/HlhNUQgnO1mupdbDyqZoGD/uCcJiYav8i/V7nJWJouHA8yq31XS2yqXp9m3VC7UZZHzUsVJA9Us5YqF0hKYeaGruIHR2bwoDF9ZFMss5t6/pzxMljU/ccYwvvRDdI7WX4o4+zLuZ6RWvsU6LGbbb0pQdB72tlV41fSefwFsk4JRdKbyV3Xjf25pV4IXOTcqhy+4JTB/jXxrF";
   users = [ user1 ];
   users = [ user1 user2 ];


   system1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE";
   system1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE";
  system2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKzxQgondgEYcLpcPdJLrTdNgZ2gznOHCAxMdaceTUT1";
   systems = [ system1 ];
   systems = [ system1 system2 ];
in
in
{
{
   "secret1.age".publicKeys = [ user1 system1 ];
   "secret1.age".publicKeys = [ user1 system1 ];
  "secret2.age".publicKeys = users ++ systems;
}
}
</nowiki>}}
</nowiki>}}
<translate>
== Usage == <!--T:19-->
<!--T:20-->
Create a secret file and encrypt it by running:
</translate>
<syntaxhighlight lang="bash">
# cd /etc/nixos/secrets
# agenix -e secret1.age
</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" />
<!--T:22-->
To reference the secret inside a NixOS module, use a configuration similar to the following example:
</translate>
<syntaxhighlight lang="nix">
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;
};
</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" />
<!--T:24-->
Secrets can also be deployed as files with specific permissions:<ref name="agenix-readme" />
</translate>
<syntaxhighlight lang="nix">
age.secrets = {
  netrc = {
    file = ./secrets/netrc.age;
    path = "/home/myuser/.netrc";
    owner = "myuser";
    group = "users";
    mode = "600";
  };
};
</syntaxhighlight>
<translate>
== Tips and tricks == <!--T:25-->
=== Replace in-place strings with secrets === <!--T:26-->
<!--T:27-->
Some modules do not yet support reading secrets from a file. Provide a placeholder string and replace it during activation.
</translate>
<syntaxhighlight lang="nix">
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"
'';
</syntaxhighlight>
<translate>
=== Access secrets inside a container === <!--T:28-->
<!--T:29-->
Expose secrets to a container by binding the decrypted file into the container filesystem:
</translate>
<syntaxhighlight lang="nix">
containers.mycontainer.bindMounts."${config.agenix.secrets.mysecret.path}".isReadOnly = true;
</syntaxhighlight>
<translate>
<!--T:30-->
Another option is to import <code>agenix.nixosModules.default</code> directly inside the container and supply the required private key:
</translate>
<syntaxhighlight lang="nix">
{ 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";
      };
    };
  };
}
</syntaxhighlight>
<translate>
=== Use secrets in the initrd === <!--T:31-->
<!--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>.
</translate>
<syntaxhighlight lang="nix">
age.secrets.hostkey-initrd = {
  file = "${paths.agenix}/hostkey-initrd.age";
  path = "/etc/initrd-hostkey";
  symlink = false;
};
boot.initrd.network.ssh.hostKeys = [ "/etc/initrd-hostkey" ];
</syntaxhighlight>
<translate>
<!--T:33-->
Rebuild the system twice and reference <code>/etc/initrd-hostkey</code> only after the file exists.
=== 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:
</translate>
<syntaxhighlight lang="nix">
age.identityPaths = [
  "/persist/etc/ssh/ssh_host_ed25519_key"
  "/persist/etc/ssh/ssh_host_rsa_key"
];
</syntaxhighlight>
<translate>
== See also == <!--T:36-->
<!--T:37-->
* [[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 == <!--T:38-->
<references />


[[Category:Applications]]
[[Category:Applications]]
[[Category:Security]]
[[Category:Security]]
</translate>