check / check (push) Waiting to run
init, vault create, unlocker add, and add, import and generate secret with --force asked for the mnemonic or passphrase, or unlocked the vault, while holding the state directory lock, so every other command that changes anything waited at the prompt. They now ask and unlock first and lock after. What they checked before asking is checked again under the lock: unlocker add fails if the vault was removed meanwhile and refuses a GPG key added meanwhile. A forced add of a secret that another command created meanwhile lets go of the lock, gets the key, and takes the lock again. secret.CreatePGPUnlocker now takes the vault, already unlocked; SetMnemonic and SetUnlockPassphrase, used only by it, are gone. Not done: secret move between vaults still unlocks both under the lock; the issue does not name it. 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.Mnemonic = mnemonic
|
|
|
|
_, err := vlt.GetSecret(testSecretName)
|
|
require.ErrorIs(t, err, vault.ErrMnemonicMismatch)
|
|
require.NotErrorIs(t, err, vault.ErrSecretNotFound)
|
|
}
|