Agenix: Difference between revisions
Fix comment Tags: Mobile edit Mobile web edit Visual edit |
Adherence to the manual |
||
Line 1: | Line 1: | ||
[https://github.com/ryantm/agenix agenix | <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 | 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> | ||
Replace <code>yourhostname</code> with the actual host name and <code>x86_64-linux</code> with the relevant system architecture. | |||
After | 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 | === Choose a public/private key === | ||
Decide which [[SSH public key authentication|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: | |||
* <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> | ||
For deployments triggered by another user, create a dedicated key pair with <code>ssh-keygen</code>. Such keys typically reside at: | |||
* <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> | ||
Refer to [[SSH public key authentication]] for more information. | |||
=== Create the | === Create the secrets === | ||
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 | 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 == | ||
Create a secret file and encrypt it by running: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
Line 87: | Line 92: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
The agenix command | 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" /> | ||
To reference the secret inside a NixOS module, use a configuration similar to the following example: | |||
To | |||
<syntaxhighlight lang="nix"> | <syntaxhighlight lang="nix"> | ||
Line 107: | Line 110: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
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 | 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 | === 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. | |||
<syntaxhighlight lang="nix"> | <syntaxhighlight lang="nix"> | ||
Line 139: | Line 140: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Access secrets inside container === | === Access secrets inside a container === | ||
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 | 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 ]; | |||
age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; | |||
age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; | |||
age.secrets."mysecret" = { | age.secrets."mysecret" = { | ||
file = ../secrets/mysecret.age; | file = ../secrets/mysecret.age; | ||
owner = "myuser"; | owner = "myuser"; | ||
}; | }; | ||
}; | }; | ||
}; | }; | ||
Line 175: | Line 170: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | === 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 <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> | ||
Rebuild the system twice and reference <code>/etc/initrd-hostkey</code> only after the file exists. | |||
=== Agenix with Impermanence === | === Agenix with Impermanence === | ||
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]] |