Agenix: Difference between revisions

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>