283 lines
8.6 KiB
Go
283 lines
8.6 KiB
Go
package vault
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/secret/internal/secret"
|
|
"git.eeqj.de/sneak/secret/pkg/agehd"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Helper function to create a vault with long-term key set up
|
|
func createTestVaultWithKey(t *testing.T, fs afero.Fs, stateDir, vaultName string) *Vault {
|
|
// Set mnemonic for testing
|
|
t.Setenv(secret.EnvMnemonic, "abandon abandon abandon abandon abandon abandon abandon abandon abandon about")
|
|
|
|
// Create vault
|
|
vault, err := CreateVault(fs, stateDir, vaultName)
|
|
require.NoError(t, err)
|
|
|
|
// Derive and store long-term key from mnemonic
|
|
mnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
ltIdentity, err := agehd.DeriveIdentity(mnemonic, 0)
|
|
require.NoError(t, err)
|
|
|
|
// Store long-term public key in vault
|
|
vaultDir, _ := vault.GetDirectory()
|
|
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
|
|
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0600)
|
|
require.NoError(t, err)
|
|
|
|
// Unlock the vault with the derived key
|
|
vault.Unlock(ltIdentity)
|
|
|
|
return vault
|
|
}
|
|
|
|
func TestVaultAddSecretCreatesVersion(t *testing.T) {
|
|
fs := afero.NewMemMapFs()
|
|
stateDir := "/test/state"
|
|
|
|
// Create vault with long-term key
|
|
vault := createTestVaultWithKey(t, fs, stateDir, "test")
|
|
|
|
// Add a secret
|
|
secretName := "test/secret"
|
|
secretValue := []byte("initial-value")
|
|
|
|
err := vault.AddSecret(secretName, secretValue, false)
|
|
require.NoError(t, err)
|
|
|
|
// Check that version directory was created
|
|
vaultDir, _ := vault.GetDirectory()
|
|
secretDir := vaultDir + "/secrets.d/test%secret"
|
|
versionsDir := secretDir + "/versions"
|
|
|
|
// Should have one version
|
|
entries, err := afero.ReadDir(fs, versionsDir)
|
|
require.NoError(t, err)
|
|
assert.Len(t, entries, 1)
|
|
|
|
// Should have current symlink
|
|
currentPath := secretDir + "/current"
|
|
exists, err := afero.Exists(fs, currentPath)
|
|
require.NoError(t, err)
|
|
assert.True(t, exists)
|
|
|
|
// Get the secret value
|
|
retrievedValue, err := vault.GetSecret(secretName)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, secretValue, retrievedValue)
|
|
}
|
|
|
|
func TestVaultAddSecretMultipleVersions(t *testing.T) {
|
|
fs := afero.NewMemMapFs()
|
|
stateDir := "/test/state"
|
|
|
|
// Create vault with long-term key
|
|
vault := createTestVaultWithKey(t, fs, stateDir, "test")
|
|
|
|
secretName := "test/secret"
|
|
|
|
// Add first version
|
|
err := vault.AddSecret(secretName, []byte("version-1"), false)
|
|
require.NoError(t, err)
|
|
|
|
// Try to add again without force - should fail
|
|
err = vault.AddSecret(secretName, []byte("version-2"), false)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "already exists")
|
|
|
|
// Add with force - should create new version
|
|
err = vault.AddSecret(secretName, []byte("version-2"), true)
|
|
require.NoError(t, err)
|
|
|
|
// Check that we have two versions
|
|
vaultDir, _ := vault.GetDirectory()
|
|
versionsDir := vaultDir + "/secrets.d/test%secret/versions"
|
|
entries, err := afero.ReadDir(fs, versionsDir)
|
|
require.NoError(t, err)
|
|
assert.Len(t, entries, 2)
|
|
|
|
// Current value should be version-2
|
|
value, err := vault.GetSecret(secretName)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("version-2"), value)
|
|
}
|
|
|
|
func TestVaultGetSecretVersion(t *testing.T) {
|
|
fs := afero.NewMemMapFs()
|
|
stateDir := "/test/state"
|
|
|
|
// Create vault with long-term key
|
|
vault := createTestVaultWithKey(t, fs, stateDir, "test")
|
|
|
|
secretName := "test/secret"
|
|
|
|
// Add multiple versions
|
|
err := vault.AddSecret(secretName, []byte("version-1"), false)
|
|
require.NoError(t, err)
|
|
|
|
// Small delay to ensure different version names
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
err = vault.AddSecret(secretName, []byte("version-2"), true)
|
|
require.NoError(t, err)
|
|
|
|
// Get versions list
|
|
vaultDir, _ := vault.GetDirectory()
|
|
secretDir := vaultDir + "/secrets.d/test%secret"
|
|
versions, err := secret.ListVersions(fs, secretDir)
|
|
require.NoError(t, err)
|
|
require.Len(t, versions, 2)
|
|
|
|
// Get specific version (first one)
|
|
firstVersion := versions[1] // Last in list is first created
|
|
value, err := vault.GetSecretVersion(secretName, firstVersion)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("version-1"), value)
|
|
|
|
// Get specific version (second one)
|
|
secondVersion := versions[0] // First in list is most recent
|
|
value, err = vault.GetSecretVersion(secretName, secondVersion)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("version-2"), value)
|
|
|
|
// Get current (empty version)
|
|
value, err = vault.GetSecretVersion(secretName, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("version-2"), value)
|
|
}
|
|
|
|
func TestVaultVersionTimestamps(t *testing.T) {
|
|
fs := afero.NewMemMapFs()
|
|
stateDir := "/test/state"
|
|
|
|
// Create vault with long-term key
|
|
vault := createTestVaultWithKey(t, fs, stateDir, "test")
|
|
|
|
// Get long-term key
|
|
ltIdentity, err := vault.GetOrDeriveLongTermKey()
|
|
require.NoError(t, err)
|
|
|
|
secretName := "test/secret"
|
|
|
|
// Add first version
|
|
beforeFirst := time.Now()
|
|
err = vault.AddSecret(secretName, []byte("version-1"), false)
|
|
require.NoError(t, err)
|
|
afterFirst := time.Now()
|
|
|
|
// Get first version metadata
|
|
vaultDir, _ := vault.GetDirectory()
|
|
secretDir := vaultDir + "/secrets.d/test%secret"
|
|
versions, err := secret.ListVersions(fs, secretDir)
|
|
require.NoError(t, err)
|
|
require.Len(t, versions, 1)
|
|
|
|
firstVersion := secret.NewSecretVersion(vault, secretName, versions[0])
|
|
err = firstVersion.LoadMetadata(ltIdentity)
|
|
require.NoError(t, err)
|
|
|
|
// Check first version timestamps
|
|
assert.NotNil(t, firstVersion.Metadata.CreatedAt)
|
|
assert.True(t, firstVersion.Metadata.CreatedAt.After(beforeFirst.Add(-time.Second)))
|
|
assert.True(t, firstVersion.Metadata.CreatedAt.Before(afterFirst.Add(time.Second)))
|
|
|
|
assert.NotNil(t, firstVersion.Metadata.NotBefore)
|
|
assert.Equal(t, int64(1), firstVersion.Metadata.NotBefore.Unix()) // Epoch + 1
|
|
assert.Nil(t, firstVersion.Metadata.NotAfter) // Still current
|
|
|
|
// Add second version
|
|
time.Sleep(10 * time.Millisecond)
|
|
beforeSecond := time.Now()
|
|
err = vault.AddSecret(secretName, []byte("version-2"), true)
|
|
require.NoError(t, err)
|
|
afterSecond := time.Now()
|
|
|
|
// Get updated versions
|
|
versions, err = secret.ListVersions(fs, secretDir)
|
|
require.NoError(t, err)
|
|
require.Len(t, versions, 2)
|
|
|
|
// Reload first version metadata (should have notAfter now)
|
|
firstVersion = secret.NewSecretVersion(vault, secretName, versions[1])
|
|
err = firstVersion.LoadMetadata(ltIdentity)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotNil(t, firstVersion.Metadata.NotAfter)
|
|
assert.True(t, firstVersion.Metadata.NotAfter.After(beforeSecond.Add(-time.Second)))
|
|
assert.True(t, firstVersion.Metadata.NotAfter.Before(afterSecond.Add(time.Second)))
|
|
|
|
// Check second version timestamps
|
|
secondVersion := secret.NewSecretVersion(vault, secretName, versions[0])
|
|
err = secondVersion.LoadMetadata(ltIdentity)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotNil(t, secondVersion.Metadata.NotBefore)
|
|
assert.True(t, secondVersion.Metadata.NotBefore.After(beforeSecond.Add(-time.Second)))
|
|
assert.True(t, secondVersion.Metadata.NotBefore.Before(afterSecond.Add(time.Second)))
|
|
assert.Nil(t, secondVersion.Metadata.NotAfter) // Current version
|
|
}
|
|
|
|
func TestVaultGetNonExistentVersion(t *testing.T) {
|
|
fs := afero.NewMemMapFs()
|
|
stateDir := "/test/state"
|
|
|
|
// Create vault with long-term key
|
|
vault := createTestVaultWithKey(t, fs, stateDir, "test")
|
|
|
|
// Add a secret
|
|
err := vault.AddSecret("test/secret", []byte("value"), false)
|
|
require.NoError(t, err)
|
|
|
|
// Try to get non-existent version
|
|
_, err = vault.GetSecretVersion("test/secret", "20991231.999")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
func TestUpdateVersionMetadata(t *testing.T) {
|
|
fs := afero.NewMemMapFs()
|
|
stateDir := "/test/state"
|
|
|
|
// Create vault with long-term key
|
|
vault := createTestVaultWithKey(t, fs, stateDir, "test")
|
|
|
|
// Get long-term key
|
|
ltIdentity, err := vault.GetOrDeriveLongTermKey()
|
|
require.NoError(t, err)
|
|
|
|
// Create a version manually to test updateVersionMetadata
|
|
secretName := "test/secret"
|
|
versionName := "20231215.001"
|
|
version := secret.NewSecretVersion(vault, secretName, versionName)
|
|
|
|
// Set initial metadata
|
|
now := time.Now()
|
|
epochPlusOne := time.Unix(1, 0)
|
|
version.Metadata.NotBefore = &epochPlusOne
|
|
version.Metadata.NotAfter = nil
|
|
|
|
// Save version
|
|
err = version.Save([]byte("test-value"))
|
|
require.NoError(t, err)
|
|
|
|
// Update metadata
|
|
version.Metadata.NotAfter = &now
|
|
err = updateVersionMetadata(fs, version, ltIdentity)
|
|
require.NoError(t, err)
|
|
|
|
// Load and verify
|
|
version2 := secret.NewSecretVersion(vault, secretName, versionName)
|
|
err = version2.LoadMetadata(ltIdentity)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotNil(t, version2.Metadata.NotAfter)
|
|
assert.Equal(t, now.Unix(), version2.Metadata.NotAfter.Unix())
|
|
}
|