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
141 lines
4.1 KiB
Go
141 lines
4.1 KiB
Go
package vault_test
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/awnumar/memguard"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/secret/internal/secret"
|
|
"sneak.berlin/go/secret/internal/vault"
|
|
)
|
|
|
|
const (
|
|
// otherMnemonic is a valid BIP39 mnemonic other than testMnemonic.
|
|
otherMnemonic = "legal winner thank year wave sausage worth useful " +
|
|
"legal winner thank yellow"
|
|
|
|
// missingName names no vault, secret or unlocker.
|
|
missingName = "missing"
|
|
)
|
|
|
|
// newErrorTestVault creates the vault testVaultName, with the secret
|
|
// testSecretName in it, on a new in-memory filesystem.
|
|
func newErrorTestVault(t *testing.T) *vault.Vault {
|
|
t.Helper()
|
|
|
|
vlt, err := vault.CreateVault(afero.NewMemMapFs(), testStateDir,
|
|
testVaultName, testMnemonicBuffer(t), nil)
|
|
require.NoError(t, err)
|
|
|
|
value := memguard.NewBufferFromBytes([]byte("value"))
|
|
t.Cleanup(value.Destroy)
|
|
|
|
require.NoError(t, vlt.AddSecret(testSecretName, value, false))
|
|
|
|
return vlt
|
|
}
|
|
|
|
// TestVaultErrors checks that each failure returns its exported error,
|
|
// wrapped or not, so that errors.Is tells it apart from the others.
|
|
func TestVaultErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
vaultDir := filepath.Join(testStateDir, "vaults.d", testVaultName)
|
|
|
|
tests := []struct {
|
|
name string
|
|
run func(vlt *vault.Vault) error
|
|
want error
|
|
}{
|
|
{"create an existing vault", func(vlt *vault.Vault) error {
|
|
_, err := vault.CreateVault(vlt.GetFilesystem(), testStateDir,
|
|
testVaultName, nil, nil)
|
|
|
|
return err
|
|
}, vault.ErrVaultExists},
|
|
{"select a missing vault", func(vlt *vault.Vault) error {
|
|
return vault.SelectVault(vlt.GetFilesystem(), testStateDir, missingName)
|
|
}, vault.ErrVaultNotFound},
|
|
{"add a nil value", func(vlt *vault.Vault) error {
|
|
return vlt.AddSecret(missingName, nil, false)
|
|
}, secret.ErrNilValueBuffer},
|
|
{"get a missing secret", func(vlt *vault.Vault) error {
|
|
_, err := vlt.GetSecret(missingName)
|
|
|
|
return err
|
|
}, vault.ErrSecretNotFound},
|
|
{"copy onto an existing secret", func(vlt *vault.Vault) error {
|
|
return vlt.CopySecretAllVersions(vlt, testSecretName, testSecretName, false)
|
|
}, vault.ErrSecretExists},
|
|
{"copy a secret without versions", func(vlt *vault.Vault) error {
|
|
const versionless = "versionless"
|
|
|
|
err := vlt.GetFilesystem().MkdirAll(
|
|
filepath.Join(vaultDir, "secrets.d", versionless), secret.DirPerms)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return vlt.CopySecretAllVersions(vlt, versionless, "copy", false)
|
|
}, vault.ErrNoVersions},
|
|
{"remove a missing unlocker", func(vlt *vault.Vault) error {
|
|
_, err := vlt.RemoveUnlocker(missingName)
|
|
|
|
return err
|
|
}, vault.ErrUnlockerNotFound},
|
|
{"select a missing unlocker", func(vlt *vault.Vault) error {
|
|
return vlt.SelectUnlocker(missingName)
|
|
}, vault.ErrUnlockerNotFound},
|
|
{"unlocker of an unknown type", func(vlt *vault.Vault) error {
|
|
fs := vlt.GetFilesystem()
|
|
|
|
err := afero.WriteFile(fs,
|
|
filepath.Join(vaultDir, "unlockers.d", "odd", "unlocker-metadata.json"),
|
|
[]byte(`{"type":"odd"}`), secret.FilePerms)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = afero.WriteFile(fs, filepath.Join(vaultDir, "current-unlocker"),
|
|
[]byte("odd"), secret.FilePerms)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = vlt.GetCurrentUnlocker()
|
|
|
|
return err
|
|
}, vault.ErrUnsupportedUnlockerType},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require.ErrorIs(t, tt.run(newErrorTestVault(t)), tt.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGetSecretWithWrongMnemonic checks that getting a secret that exists,
|
|
// from a vault the given mnemonic does not open, fails with
|
|
// ErrMnemonicMismatch through GetSecret's wrapping, and not with
|
|
// ErrSecretNotFound.
|
|
func TestGetSecretWithWrongMnemonic(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
created := newErrorTestVault(t)
|
|
|
|
mnemonic := memguard.NewBufferFromBytes([]byte(otherMnemonic))
|
|
t.Cleanup(mnemonic.Destroy)
|
|
|
|
vlt := vault.NewVault(created.GetFilesystem(), testStateDir, testVaultName)
|
|
vlt.SetMnemonic(mnemonic)
|
|
|
|
_, err := vlt.GetSecret(testSecretName)
|
|
require.ErrorIs(t, err, vault.ErrMnemonicMismatch)
|
|
require.NotErrorIs(t, err, vault.ErrSecretNotFound)
|
|
}
|