1
0
Fork 0
mirror of https://github.com/Mic92/sops-nix.git synced 2024-12-14 11:57:52 +00:00

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.
This commit is contained in:
Jeremy Fleischman 2024-11-07 15:09:38 -06:00 committed by mergify[bot]
parent fe63071416
commit c9f6b151cc
2 changed files with 44 additions and 22 deletions

View file

@ -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 {

View file

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