Leave no partial unlocker directory when adding an unlocker fails (closes #48)
check / check (push) Failing after 3s
check / check (push) Failing after 3s
CreatePGPUnlocker looked up the GPG key's fingerprint, and the keychain unlocker got the long-term key, only after writing part of the unlocker, so a failure there left a directory with no metadata. Both now do every step that can fail before writing anything. `secret unlocker add pgp` looks the fingerprint up once, for its duplicate check, and passes it to CreatePGPUnlocker to record. All four unlocker types write their files through the new secret.WriteDir, which builds a new directory in a temporary directory, renames it into place when complete and removes it on a failure. A directory that already exists, as when an unlocker replaces one of the same name, is written in place and never removed. Model: opus-5-5
This commit was merged in pull request #90.
This commit is contained in:
@@ -254,7 +254,7 @@ func CreateSecureEnclaveUnlocker(
|
||||
)
|
||||
}
|
||||
|
||||
// Step 4: Create unlocker directory and write files
|
||||
// Step 4: Prepare the unlocker directory's path and metadata
|
||||
vaultDir, err := vault.GetDirectory()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get vault directory: %w", err)
|
||||
@@ -262,23 +262,7 @@ func CreateSecureEnclaveUnlocker(
|
||||
|
||||
unlockerDirName := fmt.Sprintf("se-%s", filepath.Base(seKeyLabel))
|
||||
unlockerDir := filepath.Join(vaultDir, "unlockers.d", unlockerDirName)
|
||||
if err := fs.MkdirAll(unlockerDir, DirPerms); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to create unlocker directory: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
// Write SE-encrypted long-term key
|
||||
ltKeyPath := filepath.Join(unlockerDir, seLongtermFilename)
|
||||
if err := WriteFileAtomic(fs, ltKeyPath, encryptedLtKey); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to write SE-encrypted long-term key: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
// Write metadata
|
||||
seMetadata := SecureEnclaveUnlockerMetadata{
|
||||
UnlockerMetadata: UnlockerMetadata{
|
||||
Type: seUnlockerType,
|
||||
@@ -294,9 +278,25 @@ func CreateSecureEnclaveUnlocker(
|
||||
return nil, fmt.Errorf("failed to marshal metadata: %w", err)
|
||||
}
|
||||
|
||||
metadataPath := filepath.Join(unlockerDir, "unlocker-metadata.json")
|
||||
if err := WriteFileAtomic(fs, metadataPath, metadataBytes); err != nil {
|
||||
return nil, fmt.Errorf("failed to write metadata: %w", err)
|
||||
// Step 5: Write the SE-encrypted long-term key, then the metadata
|
||||
err = WriteDir(fs, unlockerDir, func(dir string) error {
|
||||
ltKeyPath := filepath.Join(dir, seLongtermFilename)
|
||||
if err := WriteFileAtomic(fs, ltKeyPath, encryptedLtKey); err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to write SE-encrypted long-term key: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
metadataPath := filepath.Join(dir, "unlocker-metadata.json")
|
||||
if err := WriteFileAtomic(fs, metadataPath, metadataBytes); err != nil {
|
||||
return fmt.Errorf("failed to write metadata: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SecureEnclaveUnlocker{
|
||||
|
||||
Reference in New Issue
Block a user