Skip corrupt unlocker metadata in unlocker select and remove (closes #72)
check / check (push) Waiting to run

findUnlockerByID failed on the first unlocker directory whose metadata
could not be checked, read or parsed, so `secret unlocker select` and
`remove` failed when one sorted before the unlocker asked for. It now
skips such a directory with the warning ListUnlockers gives, through
the code both now share. A skipped directory is removed by its
directory name, with RemoveDirAtomic, and cannot be selected. Removing
one whose metadata file is missing or corrupt never counts as removing
the last unlocker; removing one whose metadata file cannot be checked
for or read does, since it may be the only working unlocker.

Model: opus-5-5
This commit was merged in pull request #91.
This commit is contained in:
2026-10-04 12:42:01 +02:00
parent 1ec0423e6e
commit 24d99819a3
5 changed files with 302 additions and 73 deletions
+44 -1
View File
@@ -744,14 +744,43 @@ func (cli *Instance) removeUnlocker(
return err
}
// Get list of unlockers
// Get list of unlockers. It leaves out a directory whose metadata file
// is missing or cannot be checked for, read or parsed.
unlockers, err := vlt.ListUnlockers()
if err != nil {
return fmt.Errorf("failed to list unlockers: %w", err)
}
vaultDir, err := vlt.GetDirectory()
if err != nil {
return fmt.Errorf("failed to get vault directory: %w", err)
}
unlockersDir := filepath.Join(vaultDir, "unlockers.d")
// Check if we're removing the last unlocker
removingLast := false
if len(unlockers) == 1 {
lastID, err := findUnlockerIDByMetadata(
cli.fs, unlockersDir, unlockers[0], true)
if err != nil {
return err
}
removingLast = lastID == unlockerID
}
// unlockerID may instead name a directory left out of the list. If its
// metadata file is missing or corrupt it is not a working unlocker, so
// removing it never removes the last one. If the file cannot be checked
// for or read, the unlocker may be the only working one, so removing it
// counts as removing the last unlocker.
if metadataUnreadable(cli.fs, filepath.Join(unlockersDir, unlockerID)) {
removingLast = true
}
if removingLast {
// Check if vault has secrets
numSecrets, err := vlt.NumSecrets()
if err != nil {
@@ -785,6 +814,20 @@ func (cli *Instance) removeUnlocker(
return nil
}
// metadataUnreadable reports whether checking for or reading the metadata
// file in the unlocker directory unlockerDir fails. A missing file is not
// a failure.
func metadataUnreadable(fs afero.Fs, unlockerDir string) bool {
metadataPath := filepath.Join(unlockerDir, "unlocker-metadata.json")
exists, err := afero.Exists(fs, metadataPath)
if err == nil && exists {
_, err = afero.ReadFile(fs, metadataPath)
}
return err != nil
}
// UnlockerSelect selects an unlocker as current
func (cli *Instance) UnlockerSelect(unlockerID string) error {
release, err := vault.LockStateDir(cli.fs, cli.stateDir)