217 lines
5.5 KiB
Go
217 lines
5.5 KiB
Go
package secret
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"filippo.io/age"
|
|
"git.eeqj.de/sneak/secret/pkg/agehd"
|
|
"github.com/spf13/afero"
|
|
)
|
|
|
|
// MockVault is a test implementation of the VaultInterface
|
|
type MockVault struct {
|
|
name string
|
|
fs afero.Fs
|
|
directory string
|
|
longTermID *age.X25519Identity
|
|
}
|
|
|
|
func (m *MockVault) GetDirectory() (string, error) {
|
|
return m.directory, nil
|
|
}
|
|
|
|
func (m *MockVault) AddSecret(name string, value []byte, force bool) error {
|
|
// Simplified implementation for testing
|
|
secretDir := filepath.Join(m.directory, "secrets.d", name)
|
|
if err := m.fs.MkdirAll(secretDir, DirPerms); err != nil {
|
|
return err
|
|
}
|
|
return afero.WriteFile(m.fs, filepath.Join(secretDir, "value.age"), value, FilePerms)
|
|
}
|
|
|
|
func (m *MockVault) GetName() string {
|
|
return m.name
|
|
}
|
|
|
|
func (m *MockVault) GetFilesystem() afero.Fs {
|
|
return m.fs
|
|
}
|
|
|
|
func (m *MockVault) GetCurrentUnlocker() (Unlocker, error) {
|
|
return nil, nil // Not needed for this test
|
|
}
|
|
|
|
func (m *MockVault) CreatePassphraseUnlocker(passphrase string) (*PassphraseUnlocker, error) {
|
|
return nil, nil // Not needed for this test
|
|
}
|
|
|
|
func TestPerSecretKeyFunctionality(t *testing.T) {
|
|
// Create an in-memory filesystem for testing
|
|
fs := afero.NewMemMapFs()
|
|
|
|
// Set up test environment variables
|
|
oldMnemonic := os.Getenv(EnvMnemonic)
|
|
defer func() {
|
|
if oldMnemonic == "" {
|
|
os.Unsetenv(EnvMnemonic)
|
|
} else {
|
|
os.Setenv(EnvMnemonic, oldMnemonic)
|
|
}
|
|
}()
|
|
|
|
// Set test mnemonic for direct encryption/decryption
|
|
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
os.Setenv(EnvMnemonic, testMnemonic)
|
|
|
|
// Set up a test vault structure
|
|
baseDir := "/test-config/berlin.sneak.pkg.secret"
|
|
vaultDir := filepath.Join(baseDir, "vaults.d", "test-vault")
|
|
|
|
// Create vault directory structure
|
|
err := fs.MkdirAll(filepath.Join(vaultDir, "secrets.d"), DirPerms)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create vault directory: %v", err)
|
|
}
|
|
|
|
// Generate a long-term keypair for the vault using the test mnemonic
|
|
ltIdentity, err := agehd.DeriveIdentity(testMnemonic, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate long-term identity: %v", err)
|
|
}
|
|
|
|
// Write long-term public key
|
|
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
|
|
err = afero.WriteFile(
|
|
fs,
|
|
ltPubKeyPath,
|
|
[]byte(ltIdentity.Recipient().String()),
|
|
0600,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write long-term public key: %v", err)
|
|
}
|
|
|
|
// Set current vault
|
|
currentVaultPath := filepath.Join(baseDir, "currentvault")
|
|
err = afero.WriteFile(fs, currentVaultPath, []byte(vaultDir), FilePerms)
|
|
if err != nil {
|
|
t.Fatalf("Failed to set current vault: %v", err)
|
|
}
|
|
|
|
// Create vault instance using the mock vault
|
|
vault := &MockVault{
|
|
name: "test-vault",
|
|
fs: fs,
|
|
directory: vaultDir,
|
|
longTermID: ltIdentity,
|
|
}
|
|
|
|
// Test data
|
|
secretName := "test-secret"
|
|
secretValue := []byte("this is a test secret value")
|
|
|
|
// Test AddSecret
|
|
t.Run("AddSecret", func(t *testing.T) {
|
|
err := vault.AddSecret(secretName, secretValue, false)
|
|
if err != nil {
|
|
t.Fatalf("AddSecret failed: %v", err)
|
|
}
|
|
|
|
// Verify that all expected files were created
|
|
secretDir := filepath.Join(vaultDir, "secrets.d", secretName)
|
|
|
|
// Check value.age exists (the new per-secret key architecture format)
|
|
secretExists, err := afero.Exists(
|
|
fs,
|
|
filepath.Join(secretDir, "value.age"),
|
|
)
|
|
if err != nil || !secretExists {
|
|
t.Fatalf("value.age file was not created")
|
|
}
|
|
|
|
t.Logf("All expected files created successfully")
|
|
})
|
|
|
|
// Create a Secret object to test with
|
|
secret := NewSecret(vault, secretName)
|
|
|
|
// Test GetValue (this will need to be modified since we're using a mock vault)
|
|
t.Run("GetSecret", func(t *testing.T) {
|
|
// This test is simplified since we're not implementing the full encryption/decryption
|
|
// in the mock. We just verify the Secret object is created correctly.
|
|
if secret.Name != secretName {
|
|
t.Fatalf("Secret name doesn't match. Expected: %s, Got: %s", secretName, secret.Name)
|
|
}
|
|
|
|
if secret.vault != vault {
|
|
t.Fatalf("Secret vault reference doesn't match expected vault")
|
|
}
|
|
|
|
t.Logf("Successfully created Secret object with correct properties")
|
|
})
|
|
|
|
// Test Exists
|
|
t.Run("SecretExists", func(t *testing.T) {
|
|
exists, err := secret.Exists()
|
|
if err != nil {
|
|
t.Fatalf("Error checking if secret exists: %v", err)
|
|
}
|
|
if !exists {
|
|
t.Fatalf("Secret should exist but Exists() returned false")
|
|
}
|
|
t.Logf("Secret.Exists() works correctly")
|
|
})
|
|
}
|
|
|
|
// For testing purposes only
|
|
func isValidSecretName(name string) bool {
|
|
if name == "" {
|
|
return false
|
|
}
|
|
// Valid characters for secret names: lowercase letters, numbers, dash, dot, underscore, slash
|
|
for _, char := range name {
|
|
if (char < 'a' || char > 'z') && // lowercase letters
|
|
(char < '0' || char > '9') && // numbers
|
|
char != '-' && // dash
|
|
char != '.' && // dot
|
|
char != '_' && // underscore
|
|
char != '/' { // slash
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestSecretNameValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
valid bool
|
|
}{
|
|
{"valid-name", true},
|
|
{"valid.name", true},
|
|
{"valid_name", true},
|
|
{"valid/path/name", true},
|
|
{"123valid", true},
|
|
{"", false},
|
|
{"Invalid-Name", false}, // uppercase not allowed
|
|
{"invalid name", false}, // space not allowed
|
|
{"invalid@name", false}, // @ not allowed
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := isValidSecretName(test.name)
|
|
if result != test.valid {
|
|
t.Errorf(
|
|
"isValidSecretName(%q) = %v, want %v",
|
|
test.name,
|
|
result,
|
|
test.valid,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|