package vault_test import ( "path/filepath" "testing" "git.eeqj.de/sneak/secret/internal/secret" "git.eeqj.de/sneak/secret/internal/vault" "github.com/awnumar/memguard" "github.com/spf13/afero" "github.com/stretchr/testify/require" ) 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) }, vault.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 { return vlt.RemoveUnlocker(missingName) }, 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) }