Agenix: Difference between revisions
Adherence to the manual |
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--> | |||
=== 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--> | |||
<!--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--> | |||
=== 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--> | |||
<!--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--> | |||
<!--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--> | |||
<!--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> |