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") ''; };