diff --git a/modules/sops/default.nix b/modules/sops/default.nix index 37fa8c6..b8b4182 100644 --- a/modules/sops/default.nix +++ b/modules/sops/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, options, lib, pkgs, ... }: let cfg = config.sops; @@ -12,6 +12,8 @@ let regularSecrets = lib.filterAttrs (_: v: !v.neededForUsers) cfg.secrets; + sysusersEnabled = options.systemd ? sysusers && config.systemd.sysusers.enable; + withEnvironment = import ./with-environment.nix { inherit cfg lib; }; @@ -312,8 +314,22 @@ in { sops.environment.SOPS_GPG_EXEC = lib.mkIf (cfg.gnupg.home != null) (lib.mkDefault "${pkgs.gnupg}/bin/gpg"); + # When using sysusers we no longer be started as an activation script because those are started in initrd while sysusers is started later. + systemd.services.sops-install-secrets = lib.mkIf (regularSecrets != { } && sysusersEnabled) { + wantedBy = [ "sysinit.target" ]; + after = [ "systemd-sysusers.service" ]; + environment = cfg.environment; + unitConfig.DefaultDependencies = "no"; + + serviceConfig = { + Type = "oneshot"; + ExecStart = [ "${cfg.package}/bin/sops-install-secrets ${manifest}" ]; + RemainAfterExit = true; + }; + }; + system.activationScripts = { - setupSecrets = lib.mkIf (regularSecrets != {}) (lib.stringAfter ([ "specialfs" "users" "groups" ] ++ lib.optional cfg.age.generateKey "generate-age-key") '' + setupSecrets = lib.mkIf (regularSecrets != {} && !sysusersEnabled) (lib.stringAfter ([ "specialfs" "users" "groups" ] ++ lib.optional cfg.age.generateKey "generate-age-key") '' [ -e /run/current-system ] || echo setting up secrets... ${withEnvironment "${sops-install-secrets}/bin/sops-install-secrets ${manifest}"} '' // lib.optionalAttrs (config.system ? dryActivationScript) { diff --git a/modules/sops/secrets-for-users/default.nix b/modules/sops/secrets-for-users/default.nix index d009c80..e49ec4e 100644 --- a/modules/sops/secrets-for-users/default.nix +++ b/modules/sops/secrets-for-users/default.nix @@ -1,4 +1,4 @@ -{ lib, config, pkgs, ... }: +{ lib, options, config, pkgs, ... }: let cfg = config.sops; secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets; @@ -13,24 +13,42 @@ let secretsMountPoint = "/run/secrets-for-users.d"; symlinkPath = "/run/secrets-for-users"; }; + sysusersEnabled = options.systemd ? sysusers && config.systemd.sysusers.enable; in { - system.activationScripts = lib.mkIf (secretsForUsers != {}) { - setupSecretsForUsers = lib.mkIf (secretsForUsers != {}) (lib.stringAfter ([ "specialfs" ] ++ lib.optional cfg.age.generateKey "generate-age-key") '' - [ -e /run/current-system ] || echo setting up secrets for users... - ${withEnvironment "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}"} - '' // lib.optionalAttrs (config.system ? dryActivationScript) { - supportsDryActivation = true; - }); + systemd.services.sops-install-secrets-for-users = lib.mkIf (secretsForUsers != { } && sysusersEnabled) { + wantedBy = [ "systemd-sysusers.service" ]; + before = [ "systemd-sysusers.service" ]; + environment = cfg.environment; + unitConfig.DefaultDependencies = "no"; - users = lib.mkIf (secretsForUsers != {}) { - deps = [ "setupSecretsForUsers" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = [ "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}" ]; + RemainAfterExit = true; }; }; + system.activationScripts = lib.mkIf (secretsForUsers != { } && !sysusersEnabled) { + setupSecretsForUsers = lib.stringAfter ([ "specialfs" ] ++ lib.optional cfg.age.generateKey "generate-age-key") '' + [ -e /run/current-system ] || echo setting up secrets for users... + ${withEnvironment "${cfg.package}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}"} + '' // lib.optionalAttrs (config.system ? dryActivationScript) { + supportsDryActivation = true; + }; + + users.deps = [ "setupSecretsForUsers" ]; + }; + assertions = [{ - assertion = (lib.filterAttrs (_: v: v.owner != "root" || v.group != "root") secretsForUsers) == {}; + assertion = (lib.filterAttrs (_: v: v.owner != "root" || v.group != "root") secretsForUsers) == { }; message = "neededForUsers cannot be used for secrets that are not root-owned"; + } { + assertion = secretsForUsers != { } && sysusersEnabled -> config.users.mutableUsers; + message = '' + systemd.sysusers.enable in combination with sops.secrets..neededForUsers can only work with config.users.mutableUsers enabled. + See https://github.com/Mic92/sops-nix/issues/475 + ''; }]; system.build.sops-nix-users-manifest = manifestForUsers; diff --git a/pkgs/sops-install-secrets/nixos-test.nix b/pkgs/sops-install-secrets/nixos-test.nix index 6651626..f44f790 100644 --- a/pkgs/sops-install-secrets/nixos-test.nix +++ b/pkgs/sops-install-secrets/nixos-test.nix @@ -1,5 +1,47 @@ { makeTest ? import -, pkgs ? (import { }) }: { +, pkgs ? (import { }) }: +let + userPasswordTest = name: extraConfig: makeTest { + inherit name; + nodes.machine = { config, lib, ... }: { + imports = [ + ../../modules/sops + extraConfig + ]; + sops = { + age.keyFile = ./test-assets/age-keys.txt; + defaultSopsFile = ./test-assets/secrets.yaml; + secrets.test_key.neededForUsers = true; + secrets."nested/test/file".owner = "example-user"; + }; + + users.users.example-user = { + isNormalUser = true; + hashedPasswordFile = config.sops.secrets.test_key.path; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("multi-user.target") + + machine.succeed("getent shadow example-user | grep -q :test_value:") # password was set + machine.succeed("cat /run/secrets/nested/test/file | grep -q 'another value'") # regular secrets work... + user = machine.succeed("stat -c%U /run/secrets/nested/test/file").strip() # ...and are owned... + assert user == "example-user", f"Expected 'example-user', got '{user}'" + machine.succeed("cat /run/secrets-for-users/test_key | grep -q 'test_value'") # the user password still exists + + # BUG in nixos's overlayfs... systemd crashes on switch-to-configuration test + '' + pkgs.lib.optionalString (!(extraConfig ? system.etc.overlay.enable)) '' + machine.succeed("/run/current-system/bin/switch-to-configuration test") + machine.succeed("cat /run/secrets/nested/test/file | grep -q 'another value'") # the regular secrets still work after a switch + machine.succeed("cat /run/secrets-for-users/test_key | grep -q 'test_value'") # the user password is still present after a switch + ''; + } { + inherit pkgs; + inherit (pkgs) system; + }; +in { ssh-keys = makeTest { name = "sops-ssh-keys"; nodes.server = { ... }: { @@ -23,39 +65,6 @@ inherit (pkgs) system; }; - user-passwords = makeTest { - name = "sops-user-passwords"; - nodes.machine = { config, lib, ... }: { - imports = [ ../../modules/sops ]; - sops = { - age.keyFile = ./test-assets/age-keys.txt; - defaultSopsFile = ./test-assets/secrets.yaml; - secrets.test_key.neededForUsers = true; - secrets."nested/test/file".owner = "example-user"; - }; - - users.users.example-user = { - isNormalUser = true; - hashedPasswordFile = config.sops.secrets.test_key.path; - }; - }; - - testScript = '' - start_all() - machine.succeed("getent shadow example-user | grep -q :test_value:") # password was set - machine.succeed("cat /run/secrets/nested/test/file | grep -q 'another value'") # regular secrets work... - machine.succeed("[ $(stat -c%U /run/secrets/nested/test/file) = example-user ]") # ...and are owned... - machine.succeed("cat /run/secrets-for-users/test_key | grep -q 'test_value'") # the user password still exists - - machine.succeed("/run/current-system/bin/switch-to-configuration test") - machine.succeed("cat /run/secrets/nested/test/file | grep -q 'another value'") # the regular secrets still work after a switch - machine.succeed("cat /run/secrets-for-users/test_key | grep -q 'test_value'") # the user password is still present after a switch - ''; - } { - inherit pkgs; - inherit (pkgs) system; - }; - pruning = makeTest { name = "sops-pruning"; nodes.machine = { lib, ... }: { @@ -370,4 +379,14 @@ inherit pkgs; inherit (pkgs) system; }; + + user-passwords = userPasswordTest "sops-user-passwords" {}; +} // pkgs.lib.optionalAttrs (pkgs.lib.versionAtLeast (pkgs.lib.versions.majorMinor pkgs.lib.version) "24.05") { + user-passwords-sysusers = userPasswordTest "sops-user-passwords-sysusers" { + systemd.sysusers.enable = true; + users.mutableUsers = true; + system.etc.overlay.enable = true; + boot.initrd.systemd.enable = true; + boot.kernelPackages = pkgs.linuxPackages_latest; + }; }