Refactor vault functionality to dedicated package, fix import cycles with interface pattern, fix tests
This commit is contained in:
+48
-16
@@ -14,19 +14,29 @@ import (
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// VaultInterface defines the interface that vault implementations must satisfy
|
||||
type VaultInterface interface {
|
||||
GetDirectory() (string, error)
|
||||
AddSecret(name string, value []byte, force bool) error
|
||||
GetName() string
|
||||
GetFilesystem() afero.Fs
|
||||
GetCurrentUnlockKey() (UnlockKey, error)
|
||||
CreatePassphraseKey(passphrase string) (*PassphraseUnlockKey, error)
|
||||
}
|
||||
|
||||
// Secret represents a secret in a vault
|
||||
type Secret struct {
|
||||
Name string
|
||||
Directory string
|
||||
Metadata SecretMetadata
|
||||
vault *Vault
|
||||
vault VaultInterface
|
||||
}
|
||||
|
||||
// NewSecret creates a new Secret instance
|
||||
func NewSecret(vault *Vault, name string) *Secret {
|
||||
func NewSecret(vault VaultInterface, name string) *Secret {
|
||||
DebugWith("Creating new secret instance",
|
||||
slog.String("secret_name", name),
|
||||
slog.String("vault_name", vault.Name),
|
||||
slog.String("vault_name", vault.GetName()),
|
||||
)
|
||||
|
||||
// Convert slashes to percent signs for storage directory name
|
||||
@@ -56,7 +66,7 @@ func NewSecret(vault *Vault, name string) *Secret {
|
||||
func (s *Secret) Save(value []byte, force bool) error {
|
||||
DebugWith("Saving secret",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("vault_name", s.vault.Name),
|
||||
slog.String("vault_name", s.vault.GetName()),
|
||||
slog.Int("value_length", len(value)),
|
||||
slog.Bool("force", force),
|
||||
)
|
||||
@@ -75,7 +85,7 @@ func (s *Secret) Save(value []byte, force bool) error {
|
||||
func (s *Secret) GetValue(unlockKey UnlockKey) ([]byte, error) {
|
||||
DebugWith("Getting secret value",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("vault_name", s.vault.Name),
|
||||
slog.String("vault_name", s.vault.GetName()),
|
||||
)
|
||||
|
||||
// Check if secret exists
|
||||
@@ -85,7 +95,7 @@ func (s *Secret) GetValue(unlockKey UnlockKey) ([]byte, error) {
|
||||
return nil, fmt.Errorf("failed to check if secret exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
Debug("Secret not found during GetValue", "secret_name", s.Name, "vault_name", s.vault.Name)
|
||||
Debug("Secret not found during GetValue", "secret_name", s.Name, "vault_name", s.vault.GetName())
|
||||
return nil, fmt.Errorf("secret %s not found", s.Name)
|
||||
}
|
||||
|
||||
@@ -133,7 +143,7 @@ func (s *Secret) GetValue(unlockKey UnlockKey) ([]byte, error) {
|
||||
encryptedLtPrivKeyPath := filepath.Join(unlockKey.GetDirectory(), "longterm.age")
|
||||
Debug("Reading encrypted long-term private key", "path", encryptedLtPrivKeyPath)
|
||||
|
||||
encryptedLtPrivKey, err := afero.ReadFile(s.vault.fs, encryptedLtPrivKeyPath)
|
||||
encryptedLtPrivKey, err := afero.ReadFile(s.vault.GetFilesystem(), encryptedLtPrivKeyPath)
|
||||
if err != nil {
|
||||
Debug("Failed to read encrypted long-term private key", "error", err, "path", encryptedLtPrivKeyPath)
|
||||
return nil, fmt.Errorf("failed to read encrypted long-term private key: %w", err)
|
||||
@@ -169,14 +179,14 @@ func (s *Secret) GetValue(unlockKey UnlockKey) ([]byte, error) {
|
||||
func (s *Secret) decryptWithLongTermKey(ltIdentity *age.X25519Identity) ([]byte, error) {
|
||||
DebugWith("Decrypting secret with long-term key using per-secret architecture",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("vault_name", s.vault.Name),
|
||||
slog.String("vault_name", s.vault.GetName()),
|
||||
)
|
||||
|
||||
// Step 1: Read the secret's encrypted private key from priv.age
|
||||
encryptedSecretPrivKeyPath := filepath.Join(s.Directory, "priv.age")
|
||||
Debug("Reading encrypted secret private key", "path", encryptedSecretPrivKeyPath)
|
||||
|
||||
encryptedSecretPrivKey, err := afero.ReadFile(s.vault.fs, encryptedSecretPrivKeyPath)
|
||||
encryptedSecretPrivKey, err := afero.ReadFile(s.vault.GetFilesystem(), encryptedSecretPrivKeyPath)
|
||||
if err != nil {
|
||||
Debug("Failed to read encrypted secret private key", "error", err, "path", encryptedSecretPrivKeyPath)
|
||||
return nil, fmt.Errorf("failed to read encrypted secret private key: %w", err)
|
||||
@@ -212,7 +222,7 @@ func (s *Secret) decryptWithLongTermKey(ltIdentity *age.X25519Identity) ([]byte,
|
||||
encryptedValuePath := filepath.Join(s.Directory, "value.age")
|
||||
Debug("Reading encrypted secret value", "path", encryptedValuePath)
|
||||
|
||||
encryptedValue, err := afero.ReadFile(s.vault.fs, encryptedValuePath)
|
||||
encryptedValue, err := afero.ReadFile(s.vault.GetFilesystem(), encryptedValuePath)
|
||||
if err != nil {
|
||||
Debug("Failed to read encrypted secret value", "error", err, "path", encryptedValuePath)
|
||||
return nil, fmt.Errorf("failed to read encrypted secret value: %w", err)
|
||||
@@ -243,7 +253,7 @@ func (s *Secret) decryptWithLongTermKey(ltIdentity *age.X25519Identity) ([]byte,
|
||||
func (s *Secret) LoadMetadata() error {
|
||||
DebugWith("Loading secret metadata",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("vault_name", s.vault.Name),
|
||||
slog.String("vault_name", s.vault.GetName()),
|
||||
)
|
||||
|
||||
vaultDir, err := s.vault.GetDirectory()
|
||||
@@ -262,7 +272,7 @@ func (s *Secret) LoadMetadata() error {
|
||||
)
|
||||
|
||||
// Read metadata file
|
||||
metadataBytes, err := afero.ReadFile(s.vault.fs, metadataPath)
|
||||
metadataBytes, err := afero.ReadFile(s.vault.GetFilesystem(), metadataPath)
|
||||
if err != nil {
|
||||
Debug("Failed to read secret metadata file", "error", err, "metadata_path", metadataPath)
|
||||
return fmt.Errorf("failed to read metadata: %w", err)
|
||||
@@ -300,14 +310,14 @@ func (s *Secret) GetMetadata() SecretMetadata {
|
||||
func (s *Secret) GetEncryptedData() ([]byte, error) {
|
||||
DebugWith("Getting encrypted secret data",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("vault_name", s.vault.Name),
|
||||
slog.String("vault_name", s.vault.GetName()),
|
||||
)
|
||||
|
||||
secretPath := filepath.Join(s.Directory, "value.age")
|
||||
|
||||
Debug("Reading encrypted secret file", "secret_path", secretPath)
|
||||
|
||||
encryptedData, err := afero.ReadFile(s.vault.fs, secretPath)
|
||||
encryptedData, err := afero.ReadFile(s.vault.GetFilesystem(), secretPath)
|
||||
if err != nil {
|
||||
Debug("Failed to read encrypted secret file", "error", err, "secret_path", secretPath)
|
||||
return nil, fmt.Errorf("failed to read encrypted secret: %w", err)
|
||||
@@ -325,14 +335,14 @@ func (s *Secret) GetEncryptedData() ([]byte, error) {
|
||||
func (s *Secret) Exists() (bool, error) {
|
||||
DebugWith("Checking if secret exists",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("vault_name", s.vault.Name),
|
||||
slog.String("vault_name", s.vault.GetName()),
|
||||
)
|
||||
|
||||
secretPath := filepath.Join(s.Directory, "value.age")
|
||||
|
||||
Debug("Checking secret file existence", "secret_path", secretPath)
|
||||
|
||||
exists, err := afero.Exists(s.vault.fs, secretPath)
|
||||
exists, err := afero.Exists(s.vault.GetFilesystem(), secretPath)
|
||||
if err != nil {
|
||||
Debug("Failed to check secret file existence", "error", err, "secret_path", secretPath)
|
||||
return false, err
|
||||
@@ -345,3 +355,25 @@ func (s *Secret) Exists() (bool, error) {
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// GetCurrentVault gets the current vault from the file system
|
||||
// This function is a wrapper around the actual implementation in the vault package
|
||||
// and exists to break the import cycle.
|
||||
func GetCurrentVault(fs afero.Fs, stateDir string) (VaultInterface, error) {
|
||||
// This is a forward declaration. The actual implementation is provided
|
||||
// by the vault package when it calls RegisterGetCurrentVaultFunc.
|
||||
if getCurrentVaultFunc == nil {
|
||||
return nil, fmt.Errorf("GetCurrentVault function not registered")
|
||||
}
|
||||
return getCurrentVaultFunc(fs, stateDir)
|
||||
}
|
||||
|
||||
// getCurrentVaultFunc is a function variable that will be set by the vault package
|
||||
// to implement the actual GetCurrentVault functionality
|
||||
var getCurrentVaultFunc func(fs afero.Fs, stateDir string) (VaultInterface, error)
|
||||
|
||||
// RegisterGetCurrentVaultFunc allows the vault package to register its implementation
|
||||
// of GetCurrentVault to break the import cycle
|
||||
func RegisterGetCurrentVaultFunc(fn func(fs afero.Fs, stateDir string) (VaultInterface, error)) {
|
||||
getCurrentVaultFunc = fn
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user