Say the mnemonic still opens a vault its unlocker cannot (closes #47)
check / check (push) Failing after 2s
check / check (push) Failing after 2s
When a vault cannot be opened through its current unlocker, the error now ends by naming the vault, saying that it still opens with its mnemonic, and that 'secret unlocker add passphrase' run with SB_SECRET_MNEMONIC set gives it a new unlocker, after 'secret vault select' when it is not the current vault. Only when the vault metadata records the key the mnemonic derives, and not when the passphrase could not be read. 'secret encrypt' and 'secret decrypt' read the key secret through vault.GetSecret, and Secret.GetValue with its helpers is removed. An unreadable 'current' file's error names 'secret version list' and 'secret version promote'. Causes stay wrapped. Model: opus-5-5
This commit is contained in:
+37
-2
@@ -1,6 +1,7 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
@@ -98,7 +99,8 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) {
|
||||
if err != nil {
|
||||
secret.Debug("Failed to get current unlocker", "error", err, "vault_name", v.Name)
|
||||
|
||||
return nil, fmt.Errorf("failed to get current unlocker: %w", err)
|
||||
return nil, v.withMnemonicAdvice(
|
||||
fmt.Errorf("failed to get current unlocker: %w", err))
|
||||
}
|
||||
|
||||
secret.DebugWith("Retrieved current unlocker for vault unlock",
|
||||
@@ -112,7 +114,7 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) {
|
||||
// Other unlockers return their own identity, used to decrypt longterm.age.
|
||||
ltIdentity, err := v.unlockLongTermKey(unlocker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, v.withMnemonicAdvice(err)
|
||||
}
|
||||
|
||||
secret.DebugWith("Successfully obtained long-term identity via unlocker",
|
||||
@@ -295,3 +297,36 @@ func (v *Vault) unlockLongTermKey(
|
||||
|
||||
return ltIdentity, nil
|
||||
}
|
||||
|
||||
// withMnemonicAdvice returns err, a failure to get the long-term key through
|
||||
// the current unlocker, with advice added: that the mnemonic still opens the
|
||||
// vault, and how to give it a new unlocker. The advice is added only when the
|
||||
// vault metadata records the key that the mnemonic derives; a vault created
|
||||
// without a mnemonic records none, and without its metadata the key cannot
|
||||
// be derived. It is not added when the passphrase could not be read: the
|
||||
// unlocker was not tried, and adding one would need a passphrase read the
|
||||
// same way.
|
||||
func (v *Vault) withMnemonicAdvice(err error) error {
|
||||
if errors.Is(err, secret.ErrPassphraseNotRead) {
|
||||
return err
|
||||
}
|
||||
|
||||
vaultDir, _ := v.GetDirectory()
|
||||
|
||||
metadata, metadataErr := LoadVaultMetadata(v.fs, vaultDir)
|
||||
if metadataErr != nil || metadata.PublicKeyHash == "" {
|
||||
return err
|
||||
}
|
||||
|
||||
// 'secret unlocker add' acts on the current vault only.
|
||||
steps := "'secret unlocker add passphrase'"
|
||||
|
||||
current, currentErr := GetCurrentVault(v.fs, v.stateDir)
|
||||
if currentErr != nil || current.Name != v.Name {
|
||||
steps = fmt.Sprintf("'secret vault select %s', then %s", v.Name, steps)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w; the vault '%s' still opens with its mnemonic: run "+
|
||||
"%s with %s set to the mnemonic to give it a new unlocker",
|
||||
err, v.Name, steps, secret.EnvMnemonic)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user