From d838022691d3dde2470b7e56b6c24ac77c16df0f Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Mon, 11 Nov 2024 00:18:56 -0600 Subject: [PATCH] Demonstration of how system activation fails if the template path is not "external" This demonstrates one way of reproducing https://github.com/Mic92/sops-nix/issues/659. There may be others: I think you could achieve the same thing with a regular secret rather than a template. Here's how this fails: $ nix build .#checks.x86_64-linux.template-path-not-external -L ... vm-test-run-sops-template-path-not-external> machine: must succeed: /run/current-system/bin/switch-to-configuration test vm-test-run-sops-template-path-not-external> machine # [ 11.028130] nixos[949]: switching to system configuration /nix/store/0jbh5rys5x1br66s4n777jq8ny2bq9nb-nixos-system-machine-test vm-test-run-sops-template-path-not-external> machine # [ 11.029653] systemd[1]: Stopped target Local File Systems. vm-test-run-sops-template-path-not-external> machine # [ 11.031619] systemd[1]: Stopped target Remote File Systems. vm-test-run-sops-template-path-not-external> machine # activating the configuration... vm-test-run-sops-template-path-not-external> machine # /nix/store/8788mz0w701ff12zpsgnbm7nzsfy290k-sops-install-secrets-0.0.1/bin/sops-install-secrets: failed to prepare new secrets directory: cannot access /run/secrets: readlink /run/secrets: invalid argument vm-test-run-sops-template-path-not-external> machine # Failed to run activate script ... The only fix I can think of is to check if the chosen path is inside of `/run/secrets` and reject it if it is (of course allowing for the default value for these paths, which actually *is* inside of `/run/secrets`). --- modules/sops/default.nix | 2 +- pkgs/sops-install-secrets/main.go | 10 +++---- pkgs/sops-install-secrets/nixos-test.nix | 37 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/modules/sops/default.nix b/modules/sops/default.nix index bdd2f803..7a2ae7a1 100644 --- a/modules/sops/default.nix +++ b/modules/sops/default.nix @@ -354,7 +354,7 @@ in { sops.environment.SOPS_GPG_EXEC = lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != []) (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. + # When using sysusers we no longer are started as an activation script because those are started in initrd while sysusers is started later. systemd.services.sops-install-secrets = lib.mkIf (regularSecrets != { } && useSystemdActivation) { wantedBy = [ "sysinit.target" ]; after = [ "systemd-sysusers.service" ]; diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go index 391eb7d8..b398b234 100644 --- a/pkgs/sops-install-secrets/main.go +++ b/pkgs/sops-install-secrets/main.go @@ -185,7 +185,7 @@ func linksAreEqual(linkTarget, targetFile string, info os.FileInfo, owner int, g return linkTarget == targetFile && validUG } -func symlinkSecret(targetFile string, path string, owner int, group int, userMode bool) error { +func createSymlink(targetFile string, path string, owner int, group int, userMode bool) error { for { stat, err := os.Lstat(path) if os.IsNotExist(err) { @@ -217,7 +217,7 @@ func symlinkSecret(targetFile string, path string, owner int, group int, userMod } } -func symlinkSecrets(targetDir string, secrets []secret, templates map[string]*template, userMode bool) error { +func symlinkSecretsAndTemplates(targetDir string, secrets []secret, templates map[string]*template, userMode bool) error { for _, secret := range secrets { targetFile := filepath.Join(targetDir, secret.Name) if targetFile == secret.Path { @@ -227,7 +227,7 @@ func symlinkSecrets(targetDir string, secrets []secret, templates map[string]*te if err := os.MkdirAll(parent, os.ModePerm); err != nil { return fmt.Errorf("cannot create parent directory of '%s': %w", secret.Path, err) } - if err := symlinkSecret(targetFile, secret.Path, secret.owner, secret.group, userMode); err != nil { + if err := createSymlink(targetFile, secret.Path, secret.owner, secret.group, userMode); err != nil { return fmt.Errorf("failed to symlink secret '%s': %w", secret.Path, err) } } @@ -241,7 +241,7 @@ func symlinkSecrets(targetDir string, secrets []secret, templates map[string]*te if err := os.MkdirAll(parent, os.ModePerm); err != nil { return fmt.Errorf("cannot create parent directory of '%s': %w", template.Path, err) } - if err := symlinkSecret(targetFile, template.Path, template.owner, template.group, userMode); err != nil { + if err := createSymlink(targetFile, template.Path, template.owner, template.group, userMode); err != nil { return fmt.Errorf("failed to symlink template '%s': %w", template.Path, err) } } @@ -1302,7 +1302,7 @@ func installSecrets(args []string) error { if isDry { return nil } - if err := symlinkSecrets(manifest.SymlinkPath, manifest.Secrets, manifest.Templates, manifest.UserMode); err != nil { + if err := symlinkSecretsAndTemplates(manifest.SymlinkPath, manifest.Secrets, manifest.Templates, manifest.UserMode); err != nil { return fmt.Errorf("failed to prepare symlinks to secret store: %w", err) } if err := atomicSymlink(*secretDir, manifest.SymlinkPath); err != nil { diff --git a/pkgs/sops-install-secrets/nixos-test.nix b/pkgs/sops-install-secrets/nixos-test.nix index 765d8be7..5b4d8e67 100644 --- a/pkgs/sops-install-secrets/nixos-test.nix +++ b/pkgs/sops-install-secrets/nixos-test.nix @@ -331,6 +331,43 @@ in { ''; }; + template-path-not-external = testers.runNixOSTest { + name = "sops-template-path-not-external"; + nodes.machine = {config, ...}: { + imports = [ ../../modules/sops ]; + sops = { + age.keyFile = "/run/age-keys.txt"; + defaultSopsFile = ./test-assets/secrets.yaml; + secrets.test_key = { }; + }; + + # Must run before sops sets up keys. + boot.initrd.postDeviceCommands = '' + cp -r ${./test-assets/age-keys.txt} /run/age-keys.txt + chmod -R 700 /run/age-keys.txt + ''; + + sops.templates.test_template = { + content = '' + Test value: ${config.sops.placeholder.test_key} + ''; + # This path is inside of `/run/secrets`, which isn't allowed. + path = "/run/secrets/foo-bar"; + }; + + system.switch.enable = true; + }; + + testScript = '' + start_all() + machine.wait_for_unit("multi-user.target") + # TODO: is there some way to assert that system activation worked during + # the initial boot? We don't actually have to try activating again + # here... + machine.succeed("/run/current-system/bin/switch-to-configuration test") + ''; + }; + restart-and-reload = testers.runNixOSTest { name = "sops-restart-and-reload"; nodes.machine = {config, ...}: {