handle /run/secrets more gracefully if its a directory

This commit is contained in:
Jörg Thalheim 2025-01-10 10:42:54 +01:00 committed by Jörg Thalheim
parent 74b9fe5d7f
commit f214c1b76c
2 changed files with 30 additions and 25 deletions

View file

@ -170,6 +170,9 @@ in
boot.initrd.postDeviceCommands = '' boot.initrd.postDeviceCommands = ''
cp -r ${testAssets + "/age-keys.txt"} /run/age-keys.txt cp -r ${testAssets + "/age-keys.txt"} /run/age-keys.txt
chmod -R 700 /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
''; '';
}; };

View file

@ -70,19 +70,19 @@ type template struct {
} }
type manifest struct { type manifest struct {
Secrets []secret `json:"secrets"` Secrets []secret `json:"secrets"`
Templates []template `json:"templates"` Templates []template `json:"templates"`
PlaceholderBySecretName map[string]string `json:"placeholderBySecretName"` PlaceholderBySecretName map[string]string `json:"placeholderBySecretName"`
SecretsMountPoint string `json:"secretsMountPoint"` SecretsMountPoint string `json:"secretsMountPoint"`
SymlinkPath string `json:"symlinkPath"` SymlinkPath string `json:"symlinkPath"`
KeepGenerations int `json:"keepGenerations"` KeepGenerations int `json:"keepGenerations"`
SSHKeyPaths []string `json:"sshKeyPaths"` SSHKeyPaths []string `json:"sshKeyPaths"`
GnupgHome string `json:"gnupgHome"` GnupgHome string `json:"gnupgHome"`
AgeKeyFile string `json:"ageKeyFile"` AgeKeyFile string `json:"ageKeyFile"`
AgeSSHKeyPaths []string `json:"ageSshKeyPaths"` AgeSSHKeyPaths []string `json:"ageSshKeyPaths"`
UseTmpfs bool `json:"useTmpfs"` UseTmpfs bool `json:"useTmpfs"`
UserMode bool `json:"userMode"` UserMode bool `json:"userMode"`
Logging loggingConfig `json:"logging"` Logging loggingConfig `json:"logging"`
} }
type secretFile struct { type secretFile struct {
@ -379,7 +379,13 @@ func prepareSecretsDir(secretMountpoint string, linkName string, keysGID int, us
} }
} }
} else if !os.IsNotExist(err) { } 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++ generation++
dir := filepath.Join(secretMountpoint, strconv.Itoa(int(generation))) dir := filepath.Join(secretMountpoint, strconv.Itoa(int(generation)))
@ -718,39 +724,35 @@ func (app *appContext) validateManifest() error {
func atomicSymlink(oldname, newname string) error { func atomicSymlink(oldname, newname string) error {
if err := os.MkdirAll(filepath.Dir(newname), 0o755); err != nil { 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 // Fast path: if newname does not exist yet, we can skip the whole dance
// below. // below.
if err := os.Symlink(oldname, newname); err == nil || !os.IsExist(err) { 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, // We need to use ioutil.TempDir, as we cannot overwrite a ioutil.TempFile,
// and removing+symlinking creates a TOCTOU race. // and removing+symlinking creates a TOCTOU race.
d, err := os.MkdirTemp(filepath.Dir(newname), "."+filepath.Base(newname)) d, err := os.MkdirTemp(filepath.Dir(newname), "."+filepath.Base(newname))
if err != nil { if err != nil {
return err return fmt.Errorf("cannot create temporary directory: %w", err)
} }
cleanup := true
defer func() { defer func() {
if cleanup { os.RemoveAll(d)
os.RemoveAll(d)
}
}() }()
symlink := filepath.Join(d, "tmp.symlink") symlink := filepath.Join(d, "tmp.symlink")
if err := os.Symlink(oldname, symlink); err != nil { 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 { 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 nil
return os.RemoveAll(d)
} }
func pruneGenerations(secretsMountPoint, secretsDir string, keepGenerations int) error { func pruneGenerations(secretsMountPoint, secretsDir string, keepGenerations int) error {