|
|
|
@@ -0,0 +1,85 @@
|
|
|
|
|
//nolint:testpackage // white-box test of unexported internals
|
|
|
|
|
package cli
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/awnumar/memguard"
|
|
|
|
|
"github.com/spf13/afero"
|
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
"sneak.berlin/go/secret/internal/vault"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// errRenameFailed is returned by renameFailFs in place of a successful
|
|
|
|
|
// rename.
|
|
|
|
|
var errRenameFailed = errors.New("permission denied")
|
|
|
|
|
|
|
|
|
|
// renameFailFs fails every Rename of one path, as a read-only parent
|
|
|
|
|
// directory does.
|
|
|
|
|
type renameFailFs struct {
|
|
|
|
|
afero.Fs
|
|
|
|
|
|
|
|
|
|
path string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *renameFailFs) Rename(oldname, newname string) error {
|
|
|
|
|
if oldname == f.path {
|
|
|
|
|
return errRenameFailed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return f.Fs.Rename(oldname, newname)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestCrossVaultMoveFailsWhenSourceRemains is a regression test for
|
|
|
|
|
// https://git.eeqj.de/sneak/secret/issues/159, where a move between two
|
|
|
|
|
// vaults whose source could not be removed reported the secret moved and
|
|
|
|
|
// succeeded. It must fail, and the copy and the source must both be there.
|
|
|
|
|
// The state directory lock refuses the wrapped filesystem, so the test
|
|
|
|
|
// calls moveSecret, which MoveSecret runs once it holds the lock.
|
|
|
|
|
func TestCrossVaultMoveFailsWhenSourceRemains(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
base := afero.NewMemMapFs()
|
|
|
|
|
|
|
|
|
|
_, err := vault.CreateVault(base, testStateDir, "work",
|
|
|
|
|
testMnemonicBuffer(t), nil)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
source, err := vault.CreateVault(base, testStateDir, "default",
|
|
|
|
|
testMnemonicBuffer(t), nil)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
err = source.AddSecret("x",
|
|
|
|
|
memguard.NewBufferFromBytes([]byte("value")), false)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
instance := NewCLIInstanceWithStateDir(&renameFailFs{
|
|
|
|
|
Fs: base,
|
|
|
|
|
path: testStateDir + "/vaults.d/default/secrets.d/x",
|
|
|
|
|
}, testStateDir)
|
|
|
|
|
instance.Mnemonic = testMnemonicBuffer(t)
|
|
|
|
|
|
|
|
|
|
err = instance.moveSecret(&cobra.Command{}, "default:x", "work:y", false)
|
|
|
|
|
require.ErrorIs(t, err, errRenameFailed)
|
|
|
|
|
require.ErrorContains(t, err, "secret 'default:x' is copied with all "+
|
|
|
|
|
"1 version(s) to 'work:y', but the source remains")
|
|
|
|
|
|
|
|
|
|
// requireValue requires that the vault vaultName holds secretName with
|
|
|
|
|
// the value stored above.
|
|
|
|
|
requireValue := func(vaultName, secretName string) {
|
|
|
|
|
vlt := vault.NewVault(base, testStateDir, vaultName)
|
|
|
|
|
vlt.Mnemonic = testMnemonicBuffer(t)
|
|
|
|
|
|
|
|
|
|
value, err := vlt.GetSecret(secretName)
|
|
|
|
|
require.NoError(t, err, vaultName+":"+secretName)
|
|
|
|
|
|
|
|
|
|
defer value.Destroy()
|
|
|
|
|
|
|
|
|
|
require.Equal(t, []byte("value"), value.Bytes())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
requireValue("default", "x")
|
|
|
|
|
requireValue("work", "y")
|
|
|
|
|
}
|