From fe63071416471abdab06caa234122932a7c4b980 Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Thu, 7 Nov 2024 13:39:26 -0600 Subject: [PATCH] Improve activation messages about rendered templates This fixes https://github.com/Mic92/sops-nix/issues/652 --- modules/sops/templates/default.nix | 1 + pkgs/sops-install-secrets/main.go | 91 +++++++++++++++++++----- pkgs/sops-install-secrets/nixos-test.nix | 15 ++-- 3 files changed, 82 insertions(+), 25 deletions(-) diff --git a/modules/sops/templates/default.nix b/modules/sops/templates/default.nix index 53c8848..3a53cb8 100644 --- a/modules/sops/templates/default.nix +++ b/modules/sops/templates/default.nix @@ -24,6 +24,7 @@ in { path = mkOption { description = "Path where the rendered file will be placed"; type = types.singleLineStr; + # Keep this in sync with `RenderedSubdir` in `pkgs/sops-install-secrets/main.go` default = "/run/secrets/rendered/${config.name}"; }; content = mkOption { diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go index 307d4d9..77ffa76 100644 --- a/pkgs/sops-install-secrets/main.go +++ b/pkgs/sops-install-secrets/main.go @@ -155,6 +155,9 @@ type appContext struct { ignorePasswd bool } +// Keep this in sync with `modules/sops/templates/default.nix` +const RenderedSubdir string = "rendered" + func readManifest(path string) (*manifest, error) { file, err := os.Open(path) if err != nil { @@ -873,7 +876,7 @@ func symlinkWalk(filename string, linkDirname string, walkFn filepath.WalkFunc) return filepath.Walk(filename, symWalkFunc) } -func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, secretDir string, secrets []secret) error { +func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, secretDir string, secrets []secret, templates map[string]*template) error { var restart []string var reload []string @@ -881,6 +884,10 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s modifiedSecrets := make(map[string]bool) removedSecrets := make(map[string]bool) + newTemplates := make(map[string]bool) + modifiedTemplates := make(map[string]bool) + removedTemplates := make(map[string]bool) + // When the symlink path does not exist yet, we are being run in stage-2-init.sh // where switch-to-configuration is not run so the services would only be restarted // the next time switch-to-configuration is run. @@ -919,6 +926,33 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s } } + // Find modified/new templates + for _, template := range templates { + oldPath := filepath.Join(symlinkPath, RenderedSubdir, template.Name) + newPath := filepath.Join(secretDir, RenderedSubdir, template.Name) + + // Read the old file + oldData, err := os.ReadFile(oldPath) + if err != nil { + if os.IsNotExist(err) { + // File did not exist before + newTemplates[template.Name] = true + continue + } + return err + } + + // Read the new file + newData, err := os.ReadFile(newPath) + if err != nil { + return err + } + + if !bytes.Equal(oldData, newData) { + modifiedTemplates[template.Name] = true + } + } + writeLines := func(list []string, file string) error { if len(list) != 0 { f, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600) @@ -952,7 +986,8 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s return nil } - // Find removed secrets + // Find removed secrets/templates. + symlinkRenderedPath := filepath.Join(symlinkPath, RenderedSubdir) err := symlinkWalk(symlinkPath, symlinkPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -960,30 +995,49 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s if info.IsDir() { return nil } - path = strings.TrimPrefix(path, symlinkPath+string(os.PathSeparator)) - for _, secret := range secrets { - if secret.Name == path { - return nil - } + + // If the path we're looking at isn't in `symlinkRenderedPath`, then + // it's a secret. + rel, err := filepath.Rel(symlinkRenderedPath, path) + if err != nil { + return err + } + isSecret := strings.HasPrefix(rel, "..") + + if isSecret { + path = strings.TrimPrefix(path, symlinkPath+string(os.PathSeparator)) + for _, secret := range secrets { + if secret.Name == path { + return nil + } + } + removedSecrets[path] = true + } else { + path = strings.TrimPrefix(path, symlinkRenderedPath+string(os.PathSeparator)) + for _, template := range templates { + if template.Name == path { + return nil + } + } + removedTemplates[path] = true } - removedSecrets[path] = true return nil }) if err != nil { return err } - // Output new/modified/removed secrets - outputChanged := func(changed map[string]bool, regularPrefix, dryPrefix string) { + // Output new/modified/removed secrets/templates + outputChanged := func(noun string, changed map[string]bool, regularPrefix, dryPrefix string) { if len(changed) > 0 { s := "" if len(changed) != 1 { s = "s" } if isDry { - fmt.Printf("%s secret%s: ", dryPrefix, s) + fmt.Printf("%s %s%s: ", dryPrefix, noun, s) } else { - fmt.Printf("%s secret%s: ", regularPrefix, s) + fmt.Printf("%s %s%s: ", regularPrefix, noun, s) } // Sort the output for deterministic behavior. @@ -996,9 +1050,12 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s fmt.Println(strings.Join(keys, ", ")) } } - outputChanged(newSecrets, "adding", "would add") - outputChanged(modifiedSecrets, "modifying", "would modify") - outputChanged(removedSecrets, "removing", "would remove") + outputChanged("secret", newSecrets, "adding", "would add") + outputChanged("secret", modifiedSecrets, "modifying", "would modify") + outputChanged("secret", removedSecrets, "removing", "would remove") + outputChanged("rendered secret", newTemplates, "adding", "would add") + outputChanged("rendered secret", modifiedTemplates, "modifying", "would modify") + outputChanged("rendered secret", removedTemplates, "removing", "would remove") return nil } @@ -1210,12 +1267,12 @@ func installSecrets(args []string) error { return fmt.Errorf("cannot write secrets: %w", err) } - if err := writeTemplates(path.Join(*secretDir, "rendered"), manifest.Templates, keysGID, manifest.UserMode); err != nil { + if err := writeTemplates(path.Join(*secretDir, RenderedSubdir), manifest.Templates, keysGID, manifest.UserMode); err != nil { return fmt.Errorf("cannot render templates: %w", err) } if !manifest.UserMode { - if err := handleModifications(isDry, manifest.Logging, manifest.SymlinkPath, *secretDir, manifest.Secrets); err != nil { + if err := handleModifications(isDry, manifest.Logging, manifest.SymlinkPath, *secretDir, manifest.Secrets, manifest.Templates); err != nil { return fmt.Errorf("cannot request units to restart: %w", err) } } diff --git a/pkgs/sops-install-secrets/nixos-test.nix b/pkgs/sops-install-secrets/nixos-test.nix index 2a36ffa..3bbaf5b 100644 --- a/pkgs/sops-install-secrets/nixos-test.nix +++ b/pkgs/sops-install-secrets/nixos-test.nix @@ -421,6 +421,7 @@ in { assertOutput( out, "adding secret: test_key", + "adding rendered secret: test_template", ) machine.succeed(": > /run/secrets/test_key") @@ -429,8 +430,7 @@ in { assertOutput( out, "modifying secret: test_key", - # This is wrong. TODO: fix https://github.com/Mic92/sops-nix/issues/652 - "removing secret: rendered/test_template", + "modifying rendered secret: test_template", ) machine.succeed(": > /run/secrets/another_key") @@ -438,8 +438,8 @@ in { out = machine.succeed("/run/current-system/bin/switch-to-configuration test") assertOutput( out, - # This is wrong. TODO: fix https://github.com/Mic92/sops-nix/issues/652 - "removing secrets: another_key, rendered/another_template, rendered/test_template", + "removing secret: another_key", + "removing rendered secret: another_template", ) with subtest("dry activation"): @@ -451,8 +451,9 @@ in { assertOutput( out, "would add secret: test_key", - # This is wrong. TODO: fix https://github.com/Mic92/sops-nix/issues/652 - "would remove secrets: another_key, rendered/another_template", + "would remove secret: another_key", + "would add rendered secret: test_template", + "would remove rendered secret: another_template", ) # Verify that we did not actually activate the new configuration. @@ -477,8 +478,6 @@ in { assertOutput( out, "would modify secret: test_key", - # This is wrong. TODO: fix https://github.com/Mic92/sops-nix/issues/652 - "would remove secret: rendered/test_template", ) machine.succeed("[ $(cat /run/secrets/test_key | wc -c) = 0 ]")