From c9f6b151cc3578f6517a5dbd152f0e29cc6d875f Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Thu, 7 Nov 2024 15:09:38 -0600 Subject: [PATCH] fix: create `template.path` symlink This fixes https://github.com/Mic92/sops-nix/issues/653. Note: `main.go` has been slowly accumulating shared logic between vanilla "secrets" and "templates". It feels to me like we could DRY up some of the logic in here by creating some shared "interface" that they both implement. I opted not to try to tackle that here, though. --- pkgs/sops-install-secrets/main.go | 53 +++++++++++++++--------- pkgs/sops-install-secrets/nixos-test.nix | 13 ++++-- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go index 77ffa76..42c9d5e 100644 --- a/pkgs/sops-install-secrets/main.go +++ b/pkgs/sops-install-secrets/main.go @@ -172,51 +172,51 @@ func readManifest(path string) (*manifest, error) { return &m, nil } -func linksAreEqual(linkTarget, targetFile string, info os.FileInfo, secret *secret) bool { +func linksAreEqual(linkTarget, targetFile string, info os.FileInfo, owner int, group int) bool { validUG := true if stat, ok := info.Sys().(*syscall.Stat_t); ok { - validUG = validUG && int(stat.Uid) == secret.owner - validUG = validUG && int(stat.Gid) == secret.group + validUG = validUG && int(stat.Uid) == owner + validUG = validUG && int(stat.Gid) == group } else { panic("Failed to cast fileInfo Sys() to *syscall.Stat_t. This is possibly an unsupported OS.") } return linkTarget == targetFile && validUG } -func symlinkSecret(targetFile string, secret *secret, userMode bool) error { +func symlinkSecret(targetFile string, path string, owner int, group int, userMode bool) error { for { - stat, err := os.Lstat(secret.Path) + stat, err := os.Lstat(path) if os.IsNotExist(err) { - if err = os.Symlink(targetFile, secret.Path); err != nil { - return fmt.Errorf("cannot create symlink '%s': %w", secret.Path, err) + if err = os.Symlink(targetFile, path); err != nil { + return fmt.Errorf("cannot create symlink '%s': %w", path, err) } if !userMode { - if err = SecureSymlinkChown(secret.Path, targetFile, secret.owner, secret.group); err != nil { - return fmt.Errorf("cannot chown symlink '%s': %w", secret.Path, err) + if err = SecureSymlinkChown(path, targetFile, owner, group); err != nil { + return fmt.Errorf("cannot chown symlink '%s': %w", path, err) } } return nil } else if err != nil { - return fmt.Errorf("cannot stat '%s': %w", secret.Path, err) + return fmt.Errorf("cannot stat '%s': %w", path, err) } if stat.Mode()&os.ModeSymlink == os.ModeSymlink { - linkTarget, err := os.Readlink(secret.Path) + linkTarget, err := os.Readlink(path) if os.IsNotExist(err) { continue } else if err != nil { - return fmt.Errorf("cannot read symlink '%s': %w", secret.Path, err) - } else if linksAreEqual(linkTarget, targetFile, stat, secret) { + return fmt.Errorf("cannot read symlink '%s': %w", path, err) + } else if linksAreEqual(linkTarget, targetFile, stat, owner, group) { return nil } } - if err := os.Remove(secret.Path); err != nil { - return fmt.Errorf("cannot override %s: %w", secret.Path, err) + if err := os.Remove(path); err != nil { + return fmt.Errorf("cannot override %s: %w", path, err) } } } -func symlinkSecrets(targetDir string, secrets []secret, userMode bool) error { - for i, secret := range secrets { +func symlinkSecrets(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 { continue @@ -225,10 +225,25 @@ func symlinkSecrets(targetDir string, secrets []secret, userMode bool) error { 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, &secrets[i], userMode); err != nil { + if err := symlinkSecret(targetFile, secret.Path, secret.owner, secret.group, userMode); err != nil { return fmt.Errorf("failed to symlink secret '%s': %w", secret.Path, err) } } + + for _, template := range templates { + targetFile := filepath.Join(targetDir, RenderedSubdir, template.Name) + if targetFile == template.Path { + continue + } + parent := filepath.Dir(template.Path) + 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 { + return fmt.Errorf("failed to symlink template '%s': %w", template.Path, err) + } + } + return nil } @@ -1280,7 +1295,7 @@ func installSecrets(args []string) error { if isDry { return nil } - if err := symlinkSecrets(manifest.SymlinkPath, manifest.Secrets, manifest.UserMode); err != nil { + if err := symlinkSecrets(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 3bbaf5b..e36bfe7 100644 --- a/pkgs/sops-install-secrets/nixos-test.nix +++ b/pkgs/sops-install-secrets/nixos-test.nix @@ -284,9 +284,12 @@ in { owner = "someuser"; group = "somegroup"; }; - sops.templates.test_default.content = '' - Test value: ${config.sops.placeholder.test_key} - ''; + sops.templates.test_default = { + content = '' + Test value: ${config.sops.placeholder.test_key} + ''; + path = "/etc/externally/linked"; + }; users.groups.somegroup = {}; users.users.someuser = { @@ -321,6 +324,10 @@ in { assertEqual(expected, rendered) assertEqual(expected_default, rendered_default) + + # Confirm that `test_default` was symlinked to the appropriate place. + realpath = machine.succeed("realpath /etc/externally/linked").strip() + assertEqual(realpath, "/run/secrets.d/1/rendered/test_default") ''; };