Agenix: Difference between revisions
imported>Onny mNo edit summary |
m →Choose a Public/Private Key: link to ssh pages |
||
(8 intermediate revisions by 6 users not shown) | |||
Line 37: | Line 37: | ||
== Configuration == | == Configuration == | ||
First create a directory where secrets are going to be stored. In this example we | === 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 57: | Line 77: | ||
} | } | ||
</nowiki>}} | </nowiki>}} | ||
== Usage == | == Usage == | ||
Line 83: | Line 101: | ||
services.nextcloud = { | services.nextcloud = { | ||
enable = true; | enable = true; | ||
package = pkgs. | 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 | 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 | 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"> | <syntaxhighlight lang="nix"> | ||
Line 111: | Line 129: | ||
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. | 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 | 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"> | <syntaxhighlight lang="nix"> | ||
Line 121: | Line 139: | ||
</syntaxhighlight> | </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 == |