uses protected memory buffers now for all secrets in ram
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -107,8 +108,13 @@ func TestVaultWithRealFilesystem(t *testing.T) {
|
||||
// Create a secret with a deeply nested path
|
||||
deepPath := "api/credentials/production/database/primary"
|
||||
secretValue := []byte("supersecretdbpassword")
|
||||
expectedValue := make([]byte, len(secretValue))
|
||||
copy(expectedValue, secretValue)
|
||||
|
||||
err = vlt.AddSecret(deepPath, secretValue, false)
|
||||
secretBuffer := memguard.NewBufferFromBytes(secretValue)
|
||||
defer secretBuffer.Destroy()
|
||||
|
||||
err = vlt.AddSecret(deepPath, secretBuffer, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add secret with deep path: %v", err)
|
||||
}
|
||||
@@ -137,9 +143,9 @@ func TestVaultWithRealFilesystem(t *testing.T) {
|
||||
t.Fatalf("Failed to retrieve deep path secret: %v", err)
|
||||
}
|
||||
|
||||
if string(retrievedValue) != string(secretValue) {
|
||||
if string(retrievedValue) != string(expectedValue) {
|
||||
t.Errorf("Retrieved value doesn't match. Expected %q, got %q",
|
||||
string(secretValue), string(retrievedValue))
|
||||
string(expectedValue), string(retrievedValue))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -368,7 +374,11 @@ func TestVaultWithRealFilesystem(t *testing.T) {
|
||||
// Add a secret to vault1
|
||||
secretName := "test-secret"
|
||||
secretValue := []byte("secret in vault1")
|
||||
if err := vault1.AddSecret(secretName, secretValue, false); err != nil {
|
||||
|
||||
secretBuffer := memguard.NewBufferFromBytes(secretValue)
|
||||
defer secretBuffer.Destroy()
|
||||
|
||||
if err := vault1.AddSecret(secretName, secretBuffer, false); err != nil {
|
||||
t.Fatalf("Failed to add secret to vault1: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,21 @@ import (
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Helper function to add a secret to vault with proper buffer protection
|
||||
func addTestSecret(t *testing.T, vault *Vault, name string, value []byte, force bool) {
|
||||
t.Helper()
|
||||
buffer := memguard.NewBufferFromBytes(value)
|
||||
defer buffer.Destroy()
|
||||
err := vault.AddSecret(name, buffer, force)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestVersionIntegrationWorkflow tests the complete version workflow
|
||||
func TestVersionIntegrationWorkflow(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
@@ -66,8 +76,7 @@ func TestVersionIntegrationWorkflow(t *testing.T) {
|
||||
|
||||
// Step 1: Create initial version
|
||||
t.Run("create_initial_version", func(t *testing.T) {
|
||||
err := vault.AddSecret(secretName, []byte("version-1-data"), false)
|
||||
require.NoError(t, err)
|
||||
addTestSecret(t, vault, secretName, []byte("version-1-data"), false)
|
||||
|
||||
// Verify secret can be retrieved
|
||||
value, err := vault.GetSecret(secretName)
|
||||
@@ -108,8 +117,7 @@ func TestVersionIntegrationWorkflow(t *testing.T) {
|
||||
firstVersionName = versions[0]
|
||||
|
||||
// Create second version
|
||||
err = vault.AddSecret(secretName, []byte("version-2-data"), true)
|
||||
require.NoError(t, err)
|
||||
addTestSecret(t, vault, secretName, []byte("version-2-data"), true)
|
||||
|
||||
// Verify new value is current
|
||||
value, err := vault.GetSecret(secretName)
|
||||
@@ -142,8 +150,7 @@ func TestVersionIntegrationWorkflow(t *testing.T) {
|
||||
t.Run("create_third_version", func(t *testing.T) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
err := vault.AddSecret(secretName, []byte("version-3-data"), true)
|
||||
require.NoError(t, err)
|
||||
addTestSecret(t, vault, secretName, []byte("version-3-data"), true)
|
||||
|
||||
// Verify we now have three versions
|
||||
secretDir := filepath.Join(vaultDir, "secrets.d", "integration%test")
|
||||
@@ -214,8 +221,7 @@ func TestVersionIntegrationWorkflow(t *testing.T) {
|
||||
secretDir := filepath.Join(vaultDir, "secrets.d", "limit%test", "versions")
|
||||
|
||||
// Create 998 versions (we already have one from the first AddSecret)
|
||||
err := vault.AddSecret(limitSecretName, []byte("initial"), false)
|
||||
require.NoError(t, err)
|
||||
addTestSecret(t, vault, limitSecretName, []byte("initial"), false)
|
||||
|
||||
// Get today's date for consistent version names
|
||||
today := time.Now().Format("20060102")
|
||||
@@ -255,7 +261,9 @@ func TestVersionIntegrationWorkflow(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
|
||||
// Try to add secret without force when it exists
|
||||
err = vault.AddSecret(secretName, []byte("should-fail"), false)
|
||||
failBuffer := memguard.NewBufferFromBytes([]byte("should-fail"))
|
||||
defer failBuffer.Destroy()
|
||||
err = vault.AddSecret(secretName, failBuffer, false)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "already exists")
|
||||
})
|
||||
@@ -272,8 +280,7 @@ func TestVersionConcurrency(t *testing.T) {
|
||||
secretName := "concurrent/test"
|
||||
|
||||
// Create initial version
|
||||
err := vault.AddSecret(secretName, []byte("initial"), false)
|
||||
require.NoError(t, err)
|
||||
addTestSecret(t, vault, secretName, []byte("initial"), false)
|
||||
|
||||
// Test concurrent reads
|
||||
t.Run("concurrent_reads", func(t *testing.T) {
|
||||
@@ -326,8 +333,10 @@ func TestVersionCompatibility(t *testing.T) {
|
||||
|
||||
// Create old-style encrypted value directly in secret directory
|
||||
testValue := []byte("legacy-value")
|
||||
testValueBuffer := memguard.NewBufferFromBytes(testValue)
|
||||
defer testValueBuffer.Destroy()
|
||||
ltRecipient := ltIdentity.Recipient()
|
||||
encrypted, err := secret.EncryptToRecipient(testValue, ltRecipient)
|
||||
encrypted, err := secret.EncryptToRecipient(testValueBuffer, ltRecipient)
|
||||
require.NoError(t, err)
|
||||
|
||||
valuePath := filepath.Join(secretDir, "value.age")
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"filippo.io/age"
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -98,11 +99,15 @@ func isValidSecretName(name string) bool {
|
||||
}
|
||||
|
||||
// AddSecret adds a secret to this vault
|
||||
func (v *Vault) AddSecret(name string, value []byte, force bool) error {
|
||||
func (v *Vault) AddSecret(name string, value *memguard.LockedBuffer, force bool) error {
|
||||
if value == nil {
|
||||
return fmt.Errorf("value buffer is nil")
|
||||
}
|
||||
|
||||
secret.DebugWith("Adding secret to vault",
|
||||
slog.String("vault_name", v.Name),
|
||||
slog.String("secret_name", name),
|
||||
slog.Int("value_length", len(value)),
|
||||
slog.Int("value_length", value.Size()),
|
||||
slog.Bool("force", force),
|
||||
)
|
||||
|
||||
@@ -195,7 +200,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error {
|
||||
// We'll update the previous version's notAfter after we save the new version
|
||||
}
|
||||
|
||||
// Save the new version
|
||||
// Save the new version - pass the LockedBuffer directly
|
||||
if err := newVersion.Save(value); err != nil {
|
||||
secret.Debug("Failed to save new version", "error", err, "version", versionName)
|
||||
|
||||
@@ -272,7 +277,10 @@ func updateVersionMetadata(fs afero.Fs, version *secret.Version, ltIdentity *age
|
||||
}
|
||||
|
||||
// Encrypt metadata to the version's public key
|
||||
encryptedMetadata, err := secret.EncryptToRecipient(metadataBytes, versionIdentity.Recipient())
|
||||
metadataBuffer := memguard.NewBufferFromBytes(metadataBytes)
|
||||
defer metadataBuffer.Destroy()
|
||||
|
||||
encryptedMetadata, err := secret.EncryptToRecipient(metadataBuffer, versionIdentity.Recipient())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt version metadata: %w", err)
|
||||
}
|
||||
|
||||
@@ -24,11 +24,21 @@ import (
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Helper function to add a secret to vault with proper buffer protection
|
||||
func addTestSecretToVault(t *testing.T, vault *Vault, name string, value []byte, force bool) {
|
||||
t.Helper()
|
||||
buffer := memguard.NewBufferFromBytes(value)
|
||||
defer buffer.Destroy()
|
||||
err := vault.AddSecret(name, buffer, force)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -65,9 +75,10 @@ func TestVaultAddSecretCreatesVersion(t *testing.T) {
|
||||
// Add a secret
|
||||
secretName := "test/secret"
|
||||
secretValue := []byte("initial-value")
|
||||
expectedValue := make([]byte, len(secretValue))
|
||||
copy(expectedValue, secretValue)
|
||||
|
||||
err := vault.AddSecret(secretName, secretValue, false)
|
||||
require.NoError(t, err)
|
||||
addTestSecretToVault(t, vault, secretName, secretValue, false)
|
||||
|
||||
// Check that version directory was created
|
||||
vaultDir, _ := vault.GetDirectory()
|
||||
@@ -88,7 +99,7 @@ func TestVaultAddSecretCreatesVersion(t *testing.T) {
|
||||
// Get the secret value
|
||||
retrievedValue, err := vault.GetSecret(secretName)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, secretValue, retrievedValue)
|
||||
assert.Equal(t, expectedValue, retrievedValue)
|
||||
}
|
||||
|
||||
func TestVaultAddSecretMultipleVersions(t *testing.T) {
|
||||
@@ -101,17 +112,17 @@ func TestVaultAddSecretMultipleVersions(t *testing.T) {
|
||||
secretName := "test/secret"
|
||||
|
||||
// Add first version
|
||||
err := vault.AddSecret(secretName, []byte("version-1"), false)
|
||||
require.NoError(t, err)
|
||||
addTestSecretToVault(t, vault, secretName, []byte("version-1"), false)
|
||||
|
||||
// Try to add again without force - should fail
|
||||
err = vault.AddSecret(secretName, []byte("version-2"), false)
|
||||
failBuffer := memguard.NewBufferFromBytes([]byte("version-2"))
|
||||
defer failBuffer.Destroy()
|
||||
err := vault.AddSecret(secretName, failBuffer, 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)
|
||||
addTestSecretToVault(t, vault, secretName, []byte("version-2"), true)
|
||||
|
||||
// Check that we have two versions
|
||||
vaultDir, _ := vault.GetDirectory()
|
||||
@@ -136,14 +147,12 @@ func TestVaultGetSecretVersion(t *testing.T) {
|
||||
secretName := "test/secret"
|
||||
|
||||
// Add multiple versions
|
||||
err := vault.AddSecret(secretName, []byte("version-1"), false)
|
||||
require.NoError(t, err)
|
||||
addTestSecretToVault(t, vault, secretName, []byte("version-1"), false)
|
||||
|
||||
// Small delay to ensure different version names
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
err = vault.AddSecret(secretName, []byte("version-2"), true)
|
||||
require.NoError(t, err)
|
||||
addTestSecretToVault(t, vault, secretName, []byte("version-2"), true)
|
||||
|
||||
// Get versions list
|
||||
vaultDir, _ := vault.GetDirectory()
|
||||
@@ -185,7 +194,9 @@ func TestVaultVersionTimestamps(t *testing.T) {
|
||||
|
||||
// Add first version
|
||||
beforeFirst := time.Now()
|
||||
err = vault.AddSecret(secretName, []byte("version-1"), false)
|
||||
v1Buffer := memguard.NewBufferFromBytes([]byte("version-1"))
|
||||
defer v1Buffer.Destroy()
|
||||
err = vault.AddSecret(secretName, v1Buffer, false)
|
||||
require.NoError(t, err)
|
||||
afterFirst := time.Now()
|
||||
|
||||
@@ -212,8 +223,7 @@ func TestVaultVersionTimestamps(t *testing.T) {
|
||||
// Add second version
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
beforeSecond := time.Now()
|
||||
err = vault.AddSecret(secretName, []byte("version-2"), true)
|
||||
require.NoError(t, err)
|
||||
addTestSecretToVault(t, vault, secretName, []byte("version-2"), true)
|
||||
afterSecond := time.Now()
|
||||
|
||||
// Get updated versions
|
||||
@@ -249,11 +259,10 @@ func TestVaultGetNonExistentVersion(t *testing.T) {
|
||||
vault := createTestVaultWithKey(t, fs, stateDir, "test")
|
||||
|
||||
// Add a secret
|
||||
err := vault.AddSecret("test/secret", []byte("value"), false)
|
||||
require.NoError(t, err)
|
||||
addTestSecretToVault(t, vault, "test/secret", []byte("value"), false)
|
||||
|
||||
// Try to get non-existent version
|
||||
_, err = vault.GetSecretVersion("test/secret", "20991231.999")
|
||||
_, err := vault.GetSecretVersion("test/secret", "20991231.999")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not found")
|
||||
}
|
||||
@@ -281,7 +290,9 @@ func TestUpdateVersionMetadata(t *testing.T) {
|
||||
version.Metadata.NotAfter = nil
|
||||
|
||||
// Save version
|
||||
err = version.Save([]byte("test-value"))
|
||||
testBuffer := memguard.NewBufferFromBytes([]byte("test-value"))
|
||||
defer testBuffer.Destroy()
|
||||
err = version.Save(testBuffer)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Update metadata
|
||||
|
||||
@@ -346,10 +346,7 @@ func (v *Vault) CreatePassphraseUnlocker(passphrase *memguard.LockedBuffer) (*se
|
||||
|
||||
// Encrypt private key with passphrase
|
||||
privKeyStr := unlockerIdentity.String()
|
||||
privKeyBuffer := memguard.NewBufferFromBytes([]byte(privKeyStr))
|
||||
defer privKeyBuffer.Destroy()
|
||||
|
||||
encryptedPrivKey, err := secret.EncryptWithPassphrase(privKeyBuffer.Bytes(), passphrase)
|
||||
encryptedPrivKey, err := secret.EncryptWithPassphrase([]byte(privKeyStr), passphrase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt unlocker private key: %w", err)
|
||||
}
|
||||
@@ -385,8 +382,10 @@ func (v *Vault) CreatePassphraseUnlocker(passphrase *memguard.LockedBuffer) (*se
|
||||
return nil, fmt.Errorf("failed to get long-term key: %w", err)
|
||||
}
|
||||
|
||||
ltPrivKey := []byte(ltIdentity.String())
|
||||
encryptedLtPrivKey, err := secret.EncryptToRecipient(ltPrivKey, unlockerIdentity.Recipient())
|
||||
ltPrivKeyBuffer := memguard.NewBufferFromBytes([]byte(ltIdentity.String()))
|
||||
defer ltPrivKeyBuffer.Destroy()
|
||||
|
||||
encryptedLtPrivKey, err := secret.EncryptToRecipient(ltPrivKeyBuffer, unlockerIdentity.Recipient())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt long-term private key: %w", err)
|
||||
}
|
||||
|
||||
@@ -122,8 +122,13 @@ func TestVaultOperations(t *testing.T) {
|
||||
// Now add a secret
|
||||
secretName := "test/secret"
|
||||
secretValue := []byte("test-secret-value")
|
||||
expectedValue := make([]byte, len(secretValue))
|
||||
copy(expectedValue, secretValue)
|
||||
|
||||
err = vlt.AddSecret(secretName, secretValue, false)
|
||||
secretBuffer := memguard.NewBufferFromBytes(secretValue)
|
||||
defer secretBuffer.Destroy()
|
||||
|
||||
err = vlt.AddSecret(secretName, secretBuffer, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add secret: %v", err)
|
||||
}
|
||||
@@ -152,8 +157,8 @@ func TestVaultOperations(t *testing.T) {
|
||||
t.Fatalf("Failed to get secret: %v", err)
|
||||
}
|
||||
|
||||
if string(retrievedValue) != string(secretValue) {
|
||||
t.Errorf("Expected secret value '%s', got '%s'", string(secretValue), string(retrievedValue))
|
||||
if string(retrievedValue) != string(expectedValue) {
|
||||
t.Errorf("Expected secret value '%s', got '%s'", string(expectedValue), string(retrievedValue))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user