From ecee2c76b9d4fb755958e6d2212a0b91a5ae51a0 Mon Sep 17 00:00:00 2001 From: Ryan Mulligan Date: Sun, 9 May 2021 14:17:48 -0700 Subject: [PATCH 1/3] fix: allow deps of installRootOwnedSecrets activation script to be overridden --- modules/age.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/age.nix b/modules/age.nix index d868a9d..a840974 100644 --- a/modules/age.nix +++ b/modules/age.nix @@ -105,7 +105,7 @@ in # Secrets with root owner and group can be installed before users # exist. This allows user password files to be encrypted. - system.activationScripts.agenixRoot = installRootOwnedSecrets; + system.activationScripts.agenixRoot.text = installRootOwnedSecrets; system.activationScripts.users.deps = [ "agenixRoot" ]; # Other secrets need to wait for users and groups to exist. From 6aec6889ba1f0cb72637928cabcfebb70950dfc7 Mon Sep 17 00:00:00 2001 From: Ryan Mulligan Date: Sun, 9 May 2021 14:18:20 -0700 Subject: [PATCH 2/3] feature: use uid 0 and gid 0 as default owner and group (consider them root) This assumes that the root user is always uid 0 and gid 0, which I believe is a safe assumption. The reason to add this is because when a declarative VM (for example, a NixOS test) or image boots the first time, the installRootOwnedSecrets activation script runs BEFORE the "users" and "groups" activation scripts, so the user and group for root is not created. Using uid 0 and gid 0 gets around the root user not being set up yet. --- modules/age.nix | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/age.nix b/modules/age.nix index a840974..2b55673 100644 --- a/modules/age.nix +++ b/modules/age.nix @@ -25,10 +25,13 @@ let mv -f "$TMP_FILE" '${secretType.path}' ''; - rootOwnedSecrets = builtins.filter (st: st.owner == "root" && st.group == "root") (builtins.attrValues cfg.secrets); + isRootSecret = st: (st.owner == "root" || st.owner == "0") && (st.group == "root" || st.group == "0"); + isNotRootSecret = st: !(isRootSecret st); + + rootOwnedSecrets = builtins.filter isRootSecret (builtins.attrValues cfg.secrets); installRootOwnedSecrets = builtins.concatStringsSep "\n" ([ "echo '[agenix] decrypting root secrets...'" ] ++ (map installSecret rootOwnedSecrets)); - nonRootSecrets = builtins.filter (st: st.owner != "root" || st.group != "root") (builtins.attrValues cfg.secrets); + nonRootSecrets = builtins.filter isNotRootSecret (builtins.attrValues cfg.secrets); installNonRootSecrets = builtins.concatStringsSep "\n" ([ "echo '[agenix] decrypting non-root secrets...'" ] ++ (map installSecret nonRootSecrets)); secretType = types.submodule ({ config, ... }: { @@ -62,14 +65,14 @@ let }; owner = mkOption { type = types.str; - default = "root"; + default = "0"; description = '' User of the file. ''; }; group = mkOption { type = types.str; - default = users.${config.owner}.group; + default = users.${config.owner}.group or "0"; description = '' Group of the file. ''; From 419c6cc28162648af06d5ba150db058770a7f504 Mon Sep 17 00:00:00 2001 From: Ryan Mulligan Date: Sun, 9 May 2021 14:22:48 -0700 Subject: [PATCH 3/3] dev: add integration test --- example/passwordfile-user1.age | 9 ++++++ example/secrets.nix | 1 + test/install_ssh_host_keys.nix | 15 +++++++++ test/integration.nix | 58 ++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 example/passwordfile-user1.age create mode 100644 test/install_ssh_host_keys.nix create mode 100644 test/integration.nix diff --git a/example/passwordfile-user1.age b/example/passwordfile-user1.age new file mode 100644 index 0000000..de43bf4 --- /dev/null +++ b/example/passwordfile-user1.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 KLPP8w s1DYZRlZuSsyhmZCF1lFB+E9vB8bZ/+ZhBRlx8nprwE +nmYVCsVBrX2CFXXPU+D+bbkkIe/foofp+xoUrg9DHZw +-> ssh-ed25519 V3XmEA Pwv3oCwcY0DX8rY48UNfsj9RumWsn4dbgorYHCwObgI +FKxRYkL3JHtJxUwymWDF0rAtJ33BivDI6IfPsfumM90 +-> V'v(/u$-grease em/Vgf 2qDuk +7I3iiQLPGi1COML9u/JeYkr7EqbSLoU +--- 57WJRigUGtmcObrssS3s4PvmR8wgh1AOC/ijJn1s3xI +'KƷY&7GOFjkXBnuJ:9(X#Aڧj,_?ZvV96]oks~%c e^C%JQ5<Hz}C,p*!WA҅dCK)-y \ No newline at end of file diff --git a/example/secrets.nix b/example/secrets.nix index 8896b56..3bdac11 100644 --- a/example/secrets.nix +++ b/example/secrets.nix @@ -5,4 +5,5 @@ in { "secret1.age".publicKeys = [ user1 system1 ]; "secret2.age".publicKeys = [ user1 ]; + "passwordfile-user1.age".publicKeys = [ user1 system1 ]; } diff --git a/test/install_ssh_host_keys.nix b/test/install_ssh_host_keys.nix new file mode 100644 index 0000000..028845b --- /dev/null +++ b/test/install_ssh_host_keys.nix @@ -0,0 +1,15 @@ +# Do not copy this! It is insecure. This is only okay because we are testing. +{ + system.activationScripts.agenixRoot.deps = [ "installSSHHostKeys" ]; + + system.activationScripts.installSSHHostKeys.text = '' + mkdir -p /etc/ssh + (umask u=rw,g=r,o=r; cp ${../example_keys/system1.pub} /etc/ssh/ssh_host_ed25519_key.pub) + ( + umask u=rw,g=,o= + cp ${../example_keys/system1} /etc/ssh/ssh_host_ed25519_key + touch /etc/ssh/ssh_host_rsa_key + ) + + ''; +} diff --git a/test/integration.nix b/test/integration.nix new file mode 100644 index 0000000..8bb234a --- /dev/null +++ b/test/integration.nix @@ -0,0 +1,58 @@ +{ +nixpkgs ? , +pkgs ? import { inherit system; config = {}; }, +system ? builtins.currentSystem +} @args: + +import "${nixpkgs}/nixos/tests/make-test-python.nix" ({ pkgs, ...}: { + name = "agenix-integration"; + + nodes.system1 = { config, lib, ... }: { + + imports = [ + ../modules/age.nix + ./install_ssh_host_keys.nix + ]; + + services.openssh.enable = true; + + age.secrets.passwordfile-user1 = { + file = ../example/passwordfile-user1.age; + }; + + users = { + mutableUsers = false; + + users = { + user1 = { + isNormalUser = true; + passwordFile = config.age.secrets.passwordfile-user1.path; + }; + }; + }; + + }; + + testScript = + let + user = "user1"; + password = "password1234"; + in '' + system1.wait_for_unit("multi-user.target") + system1.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + system1.sleep(2) + system1.send_key("alt-f2") + system1.wait_until_succeeds(f"[ $(fgconsole) = 2 ]") + system1.wait_for_unit(f"getty@tty2.service") + system1.wait_until_succeeds(f"pgrep -f 'agetty.*tty2'") + system1.wait_until_tty_matches(2, "login: ") + system1.send_chars("${user}\n") + system1.wait_until_tty_matches(2, "login: ${user}") + system1.wait_until_succeeds("pgrep login") + system1.sleep(2) + system1.send_chars("${password}\n") + system1.send_chars("whoami > /tmp/1\n") + system1.wait_for_file("/tmp/1") + assert "${user}" in system1.succeed("cat /tmp/1") + ''; +}) args