From f55e481f153f3b99b6b15c025b0644f71e9f3fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Fri, 10 Jan 2025 10:42:54 +0100 Subject: [PATCH] handle /run/secrets more gracefully if its a directory --- checks/nixos-test.nix | 3 ++ pkgs/sops-install-secrets/main.go | 52 ++++++++++++++++--------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/checks/nixos-test.nix b/checks/nixos-test.nix index 6e3985e6..95c7a125 100644 --- a/checks/nixos-test.nix +++ b/checks/nixos-test.nix @@ -170,6 +170,9 @@ in boot.initrd.postDeviceCommands = '' cp -r ${testAssets + "/age-keys.txt"} /run/age-keys.txt chmod -R 700 /run/age-keys.txt + + # if the directory exists, sops-nix should replace it with a symlink + mkdir /run/secrets ''; }; diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go index 562dd248..fa51b62f 100644 --- a/pkgs/sops-install-secrets/main.go +++ b/pkgs/sops-install-secrets/main.go @@ -70,19 +70,19 @@ type template struct { } type manifest struct { - Secrets []secret `json:"secrets"` - Templates []template `json:"templates"` - PlaceholderBySecretName map[string]string `json:"placeholderBySecretName"` - SecretsMountPoint string `json:"secretsMountPoint"` - SymlinkPath string `json:"symlinkPath"` - KeepGenerations int `json:"keepGenerations"` - SSHKeyPaths []string `json:"sshKeyPaths"` - GnupgHome string `json:"gnupgHome"` - AgeKeyFile string `json:"ageKeyFile"` - AgeSSHKeyPaths []string `json:"ageSshKeyPaths"` - UseTmpfs bool `json:"useTmpfs"` - UserMode bool `json:"userMode"` - Logging loggingConfig `json:"logging"` + Secrets []secret `json:"secrets"` + Templates []template `json:"templates"` + PlaceholderBySecretName map[string]string `json:"placeholderBySecretName"` + SecretsMountPoint string `json:"secretsMountPoint"` + SymlinkPath string `json:"symlinkPath"` + KeepGenerations int `json:"keepGenerations"` + SSHKeyPaths []string `json:"sshKeyPaths"` + GnupgHome string `json:"gnupgHome"` + AgeKeyFile string `json:"ageKeyFile"` + AgeSSHKeyPaths []string `json:"ageSshKeyPaths"` + UseTmpfs bool `json:"useTmpfs"` + UserMode bool `json:"userMode"` + Logging loggingConfig `json:"logging"` } type secretFile struct { @@ -379,7 +379,13 @@ func prepareSecretsDir(secretMountpoint string, linkName string, keysGID int, us } } } else if !os.IsNotExist(err) { - return nil, fmt.Errorf("cannot access %s: %w", linkName, err) + if _, err2 := os.Lstat(linkName); err2 != nil { + return nil, fmt.Errorf("cannot access %s: %w", linkName, err) + } + // if `/run/secrets` exists, but is not a symlink, we need to remove it + if err = os.RemoveAll(linkName); err != nil { + return nil, fmt.Errorf("cannot remove %s: %w", linkName, err) + } } generation++ dir := filepath.Join(secretMountpoint, strconv.Itoa(int(generation))) @@ -718,39 +724,35 @@ func (app *appContext) validateManifest() error { func atomicSymlink(oldname, newname string) error { if err := os.MkdirAll(filepath.Dir(newname), 0o755); err != nil { - return err + return fmt.Errorf("cannot create directory %s: %w", filepath.Dir(newname), err) } // Fast path: if newname does not exist yet, we can skip the whole dance // below. if err := os.Symlink(oldname, newname); err == nil || !os.IsExist(err) { - return err + return fmt.Errorf("cannot create symlink %s: %w", newname, err) } // We need to use ioutil.TempDir, as we cannot overwrite a ioutil.TempFile, // and removing+symlinking creates a TOCTOU race. d, err := os.MkdirTemp(filepath.Dir(newname), "."+filepath.Base(newname)) if err != nil { - return err + return fmt.Errorf("cannot create temporary directory: %w", err) } - cleanup := true defer func() { - if cleanup { - os.RemoveAll(d) - } + os.RemoveAll(d) }() symlink := filepath.Join(d, "tmp.symlink") if err := os.Symlink(oldname, symlink); err != nil { - return err + return fmt.Errorf("cannot create symlink %s: %w", symlink, err) } if err := os.Rename(symlink, newname); err != nil { - return err + return fmt.Errorf("cannot rename %s to %s: %w", symlink, newname, err) } - cleanup = false - return os.RemoveAll(d) + return nil } func pruneGenerations(secretsMountPoint, secretsDir string, keepGenerations int) error {