Make another unlocker current when the current one is removed (closes #137)
check / check (push) Waiting to run
check / check (push) Waiting to run
Removing the vault's current unlocker left current-unlocker naming it, so every command that opened the vault failed although another unlocker remained, and the error pointed at the mnemonic. RemoveUnlocker now first makes current the first other unlocker that `secret unlocker select` accepts, in the order `secret unlocker list` shows, and returns its ID, which `secret unlocker remove` prints; with none, current-unlocker is removed too. When current-unlocker names an unlocker that does not exist and the vault has another that select accepts, the error wraps ErrUnlockerNotFound and says to run `secret unlocker select`, without the mnemonic advice. Unverified: no test covers that advice for a vault other than the current one, which gets `secret vault select` first. Model: opus-5-5
This commit is contained in:
@@ -4,9 +4,11 @@
|
||||
// file the unlocker needs is missing or the passphrase is wrong, the error
|
||||
// keeps its cause and ends by saying that the mnemonic still opens that
|
||||
// vault, but only for a vault that the mnemonic does open, and not when the
|
||||
// passphrase could not be read at all. When a secret's current file is
|
||||
// missing, the error says how to make a version current again. Each test
|
||||
// that pins such advice also follows it.
|
||||
// passphrase could not be read at all. When the current unlocker does not
|
||||
// exist and the vault has another that `secret unlocker select` accepts, the
|
||||
// error says to select one instead.
|
||||
// When a secret's current file is missing, the error says how to make a
|
||||
// version current again. Each test that pins such advice also follows it.
|
||||
|
||||
package cli_test
|
||||
|
||||
@@ -222,6 +224,69 @@ func TestMoveUnlockFailureNamesVault(t *testing.T) {
|
||||
assert.Equal(t, "value", getSecret(t, c, "y"))
|
||||
}
|
||||
|
||||
// TestMissingCurrentUnlockerNamesSelect checks the error of `secret get`
|
||||
// when current-unlocker names an unlocker that does not exist and the vault
|
||||
// has another: it names the missing unlocker and says to select another
|
||||
// one, without the advice to use the mnemonic. The test then follows that
|
||||
// advice.
|
||||
func TestMissingCurrentUnlockerNamesSelect(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := newTwoVaultFs(t)
|
||||
unlockerDir, c := newUnlockTestCLI(t, fs)
|
||||
|
||||
require.NoError(t, afero.WriteFile(fs,
|
||||
filepath.Join(unlockTestVaultDir, "current-unlocker"),
|
||||
[]byte("missing"), secret.FilePerms))
|
||||
|
||||
err := c.GetSecret(discardCmd(), "x")
|
||||
|
||||
require.ErrorIs(t, err, vault.ErrUnlockerNotFound)
|
||||
require.EqualError(t, err, "failed to unlock vault: "+
|
||||
"failed to get long-term key: failed to get current unlocker: "+
|
||||
"unlocker with ID missing not found; the vault 'default' has other "+
|
||||
"unlockers: run 'secret unlocker select' with the ID of one of them "+
|
||||
"to make it the current unlocker")
|
||||
|
||||
require.NoError(t, c.UnlockerSelect(filepath.Base(unlockerDir)))
|
||||
assert.Equal(t, "value", getSecret(t, c, "x"))
|
||||
}
|
||||
|
||||
// TestMissingCurrentUnlockerNamesMnemonic checks the error of `secret get`
|
||||
// when the unlocker that current-unlocker names does not exist and the
|
||||
// vault's only other unlocker is the directory "odd", whose metadata names a
|
||||
// type that `secret unlocker select` refuses: the error ends with the advice
|
||||
// that the mnemonic still opens the vault, not with advice to select another
|
||||
// unlocker. The test then follows that advice.
|
||||
func TestMissingCurrentUnlockerNamesMnemonic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := newTwoVaultFs(t)
|
||||
unlockerDir, c := newUnlockTestCLI(t, fs)
|
||||
|
||||
require.NoError(t, fs.RemoveAll(unlockerDir))
|
||||
require.NoError(t, afero.WriteFile(fs,
|
||||
filepath.Join(unlockTestVaultDir, "unlockers.d", "odd",
|
||||
"unlocker-metadata.json"),
|
||||
[]byte(`{"type": "odd"}`), secret.FilePerms))
|
||||
|
||||
err := c.GetSecret(discardCmd(), "x")
|
||||
|
||||
var cause *os.PathError
|
||||
|
||||
require.ErrorAs(t, err, &cause)
|
||||
require.NotErrorIs(t, err, vault.ErrUnlockerNotFound)
|
||||
require.EqualError(t, err, "failed to unlock vault: "+
|
||||
"failed to get long-term key: failed to get current unlocker: "+
|
||||
"failed to read unlocker metadata: "+cause.Error()+mnemonicAdvice)
|
||||
|
||||
c.Mnemonic = testMnemonicBuffer(t)
|
||||
require.NoError(t, c.UnlockersAdd("passphrase", discardCmd()))
|
||||
|
||||
c.Mnemonic = nil
|
||||
assert.Equal(t, "value", getSecret(t, c, "x"))
|
||||
}
|
||||
|
||||
// TestPassphraseNotReadNamesNoMnemonic runs `secret get x` on the built
|
||||
// binary without SB_UNLOCK_PASSPHRASE and with a stdin that is not a
|
||||
// terminal, so the passphrase cannot be read. The unlocker was not tried,
|
||||
|
||||
Reference in New Issue
Block a user