Agenix: Difference between revisions

imported>Onny
mNo edit summary
Pigs (talk | contribs)
m Choose a Public/Private Key: link to ssh pages
 
(13 intermediate revisions by 6 users not shown)
Line 1: Line 1:
[https://github.com/ryantm/agenix agenix] is a commandline tool for managing secrets encrypted in your Nix configuration with your existing SSH keys. The project also includes the NixOS module age for adding encrypted secrets into the Nix store and decrypting them.
[https://github.com/ryantm/agenix agenix] is a commandline tool for managing secrets in your Nix configuration, 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.


== Installation ==
== Installation ==
Line 37: Line 37:
== Configuration ==
== Configuration ==


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>
=== Choose a Public/Private Key ===
 
First, we have to decide which [[SSH public key authentication|SSH public key]] to use to encrypt the secrets. (The private key will be used to decrypt the secrets when loading the NixOS configuration.)
 
Assuming that you have [[SSH]] already installed on your NixOS server (with i.e. <code>services.openssh.enable = true;</code>), you will already have two different SSH keypairs that are intended to be used by the system itself, located at:
 
* <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>
 
If you load your NixOS config using the root user, then you can use these public keys to encrypt your secrets.
 
However, if you load your NixOS config using some other user, then you will have to use <code>ssh-keygen</code> to generate a keypair for that user, which typically lives in:
 
* <code>~/.ssh/id_rsa</code> / <code>~/.ssh/id_rsa.pub</code>
* <code>~/.ssh/id_ed25519</code> / <code>~/.ssh/id_ed25519.pub</code>
 
For more information, see [[SSH_public_key_authentication|the SSH public key authentication page]].
 
=== Create the Secrets ===
 
Next, create a directory where secrets are going to be stored. In this example we are creating the directory <code>secrets</code> inside the NixOS system configuration path <code>/etc/nixos</code>


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 43: Line 63:
</syntaxhighlight>
</syntaxhighlight>


Inside the secrets directory we create a <code>secrets.nix</code> file which will be used by the agenix client as a rule file to encrypt secrets for specific users and parts of the system
Inside the secrets directory we create a <code>secrets.nix</code> file which will be used by the agenix client as a rule file to encrypt secrets for specific users and parts of the system. The following example configures access to secrets stored in <code>secret1.age</code> for the SSH public keys of <code>user1</code> and <code>system1</code>.


{{file|/etc/nixos/secrets/secrets.nix|nix|<nowiki>
{{file|/etc/nixos/secrets/secrets.nix|nix|<nowiki>
Line 57: Line 77:
}
}
</nowiki>}}
</nowiki>}}
SSH public keys for a specific user or system can be generated with <code>ssh-keygen</code>, see [[SSH_public_key_authentication|this page]] for more information. Usually the public key of your user can be found in <code>~/.ssh/id_rsa.pub</code> and the system one in <code>/etc/ssh/ssh_host_rsa_key.pub</code>.


== Usage ==
== Usage ==
Line 77: Line 95:
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
age.secrets.nextcloud = {
age.secrets.nextcloud = {
   file = /etc/nixos/secrets/secret1.age;
   file = ./secrets/secret1.age;
   owner = "nextcloud";
   owner = "nextcloud";
   group = "nextcloud";
   group = "nextcloud";
Line 83: Line 101:
services.nextcloud = {
services.nextcloud = {
   enable = true;
   enable = true;
   package = pkgs.nextcloud25;
   package = pkgs.nextcloud28;
   hostName = "localhost";
   hostName = "localhost";
   config.adminpassFile = config.age.secrets.nextcloud.path;
   config.adminpassFile = config.age.secrets.nextcloud.path;
Line 89: Line 107:
</syntaxhighlight>
</syntaxhighlight>


Here, the service [[Nextcloud]] requires a password for the administrator account. In this case, the password is stored in an age-encrypted file, so no plaintext passwords will be copied into your world-readable Nix-store. We configure <code>owner</code> and <code>group</code> names to <code>nextcloud</code> so that the webservice has the permissions to read the password wile.
Here, the service [[Nextcloud]] requires a password for the administrator account. In this case, the password is stored in an age-encrypted file, so no plaintext passwords will be copied into your world-readable Nix-store. We configure <code>owner</code> and <code>group</code> names to <code>nextcloud</code> so that the webservice has the permissions to read the password file.
 
Secrets can be also deployed as file with specific permissions to a target path. In this example the secret is sourced to <code>/home/myuser/.netrc</code> and permissions are set that only <code>myuser</code> is able to read and write the file.
 
<syntaxhighlight lang="nix">
age.secrets = {
  netrc = {
    file = ./secrets/netrc.age;
    path = "/home/myuser/.netrc";
    owner = "myuser";
    group = "users";
    mode = "600";
  };
};
</syntaxhighlight>
 
== Tips and tricks ==
 
=== Replace inplace strings with secrets ===
 
Considering that there still might be some modules which doesn't support reading secrets from a file, you could provide a placeholder string instead of a clear-text password and replace this placeholder with the secret provided by Agenix.
 
In the following example, the Dex module creates the config file <code>/run/dex/config.yaml</code> containing the placeholder string <code>@dex-user-password@</code>. The activation script will read the Agenix secret from <code>config.age.secret.dex-user-password.path</code> and replace the placeholder string with the actual secret.
 
<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>
 
=== Access secrets inside container ===
 
Using the option <code>bindMounts</code> for an example container named <code>mycontainer</code> will provide the secret file inside the container as <code>/run/agenix/mysecret</code>:
 
<syntaxhighlight lang="nix">
containers.mycontainer.bindMounts."${config.agenix.secrets.mysecret.path}".isReadOnly = true;
</syntaxhighlight>
 
Another option would be to to use the agenix-module in the nixos-container. This also allows to set the secret-owner to the users inside the container. But it is also necessary to provide the ssh-private-key to the container in order for agenix to decrypt the secret (or generate a own for the container).
 
<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 ]; # import agenix-module into the nixos-container
 
      age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; # isn't set automatically for some reason
 
      # import the secret
      age.secrets."mysecret" = {
        file = ../secrets/mysecret.age;
        owner = "myuser";
      };
 
      # use the secret like you normally would with config.age.secrets."mysecret".path
    };
  };
}
</syntaxhighlight>
 
=== Using secrets in initrd ===
 
Unfortunately this doesn't work because Agenix sets up secrets during system activation stage but initrd is being built before that. As [https://github.com/ryantm/agenix/issues/193 a workaround] we could create the secret as a static file outside of <code>/run/agenix</code> and reference the secret at <code>/etc/initrd-hostkey</code>.
 
<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>
 
For this workaround you'll have to rebuild twice and reference the secret <code>/etc/initrd-hostkey</code> only after the file is created.
 
=== Agenix with Impermanence ===
If your system is configured to be [[Impermanence|impermanent]], then it's possible the system's ssh keys won't yet be available during boot to decrypt secrets. The solution is to manually set <code>age.identityPaths</code> to the persistent paths of your keys.
<syntaxhighlight lang="nix">
 
# Direct path to persistent location of system ssh keys
 
age.identityPaths = [
 
  "/persist/etc/ssh/ssh_host_ed25519_key"
 
  "/persist/etc/ssh/ssh_host_rsa_key"
 
];
 
</syntaxhighlight>


== See also ==
== See also ==