mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-20 13:30:51 +02:00
feat: Implement storage provider management functionality
- Added new routes and handlers for managing storage providers, including creation, editing, and deletion. - Introduced a new StorageProvider form component for user input. - Enhanced the database schema to support storage provider references in transfer configurations. - Implemented encryption for sensitive fields in storage provider data. - Added tests for storage provider API endpoints and integration with the database. - Updated frontend components to support storage provider selection and testing.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package keymanager
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/starfleetcptn/gomft/internal/encryption"
|
||||
)
|
||||
|
||||
// KeyManager handles the management of encryption keys
|
||||
type KeyManager struct {
|
||||
// primaryKey is the main encryption key used for AES-256 encryption
|
||||
primaryKey []byte
|
||||
|
||||
// envVarName is the name of the environment variable that stores the key
|
||||
envVarName string
|
||||
|
||||
// mutex to protect key access
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewKeyManager creates a new KeyManager instance
|
||||
func NewKeyManager(envVarName string) *KeyManager {
|
||||
if envVarName == "" {
|
||||
envVarName = encryption.DefaultKeyEnvVar
|
||||
}
|
||||
|
||||
return &KeyManager{
|
||||
envVarName: envVarName,
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize loads the encryption key from the environment
|
||||
// and validates it meets security requirements
|
||||
func (km *KeyManager) Initialize() error {
|
||||
// Try loading .env file if exists
|
||||
_ = godotenv.Load()
|
||||
|
||||
// Get key from environment variable
|
||||
keyStr := os.Getenv(km.envVarName)
|
||||
if keyStr == "" {
|
||||
return fmt.Errorf(encryption.ErrKeyNotProvided, km.envVarName)
|
||||
}
|
||||
|
||||
// Attempt to decode the key - we support both hex and base64 formats
|
||||
var keyBytes []byte
|
||||
var err error
|
||||
|
||||
// Try hex decoding first
|
||||
keyBytes, err = hex.DecodeString(keyStr)
|
||||
if err != nil {
|
||||
// If hex decoding fails, try base64
|
||||
keyBytes, err = base64.StdEncoding.DecodeString(keyStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf(encryption.ErrInvalidKey, "key must be valid hex or base64 encoded")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate key length
|
||||
if len(keyBytes) < encryption.MinKeyLength {
|
||||
return fmt.Errorf(encryption.ErrKeyTooShort, encryption.MinKeyLength)
|
||||
}
|
||||
|
||||
// Store the key
|
||||
km.mutex.Lock()
|
||||
km.primaryKey = keyBytes
|
||||
km.mutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPrimaryKey returns the primary encryption key
|
||||
func (km *KeyManager) GetPrimaryKey() ([]byte, error) {
|
||||
km.mutex.RLock()
|
||||
defer km.mutex.RUnlock()
|
||||
|
||||
if km.primaryKey == nil || len(km.primaryKey) == 0 {
|
||||
return nil, fmt.Errorf("encryption key not initialized")
|
||||
}
|
||||
|
||||
// Return a copy of the key to prevent modification
|
||||
keyCopy := make([]byte, len(km.primaryKey))
|
||||
copy(keyCopy, km.primaryKey)
|
||||
|
||||
return keyCopy, nil
|
||||
}
|
||||
|
||||
// GenerateKey generates a new random encryption key of the specified size
|
||||
func GenerateKey(size int) ([]byte, error) {
|
||||
if size < encryption.MinKeyLength {
|
||||
size = encryption.MinKeyLength
|
||||
}
|
||||
|
||||
key := make([]byte, size)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate random key: %w", err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// GenerateKeyString generates a new random encryption key and returns it as a base64 string
|
||||
func GenerateKeyString(size int) (string, error) {
|
||||
key, err := GenerateKey(size)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(key), nil
|
||||
}
|
||||
|
||||
// ValidateKeyLength checks if the provided key meets the minimum length requirement
|
||||
func ValidateKeyLength(key []byte) bool {
|
||||
return len(key) >= encryption.MinKeyLength
|
||||
}
|
||||
|
||||
// StoreKeyEnvironment stores the encryption key in the specified environment variable
|
||||
// This is generally only used for development or testing purposes
|
||||
func (km *KeyManager) StoreKeyEnvironment(key []byte) error {
|
||||
if !ValidateKeyLength(key) {
|
||||
return fmt.Errorf(encryption.ErrKeyTooShort, encryption.MinKeyLength)
|
||||
}
|
||||
|
||||
keyStr := base64.StdEncoding.EncodeToString(key)
|
||||
err := os.Setenv(km.envVarName, keyStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set environment variable: %w", err)
|
||||
}
|
||||
|
||||
// Update the stored key
|
||||
km.mutex.Lock()
|
||||
km.primaryKey = key
|
||||
km.mutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEnvironmentVariableName returns the name of the environment variable used for the key
|
||||
func (km *KeyManager) GetEnvironmentVariableName() string {
|
||||
return km.envVarName
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package keymanager
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/starfleetcptn/gomft/internal/encryption"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewKeyManager(t *testing.T) {
|
||||
// Test with custom env var
|
||||
customEnvVar := "CUSTOM_KEY_ENV_VAR"
|
||||
km := NewKeyManager(customEnvVar)
|
||||
assert.Equal(t, customEnvVar, km.envVarName)
|
||||
|
||||
// Test with empty env var (should use default)
|
||||
km = NewKeyManager("")
|
||||
assert.Equal(t, encryption.DefaultKeyEnvVar, km.envVarName)
|
||||
}
|
||||
|
||||
func TestGenerateKey(t *testing.T) {
|
||||
// Test generating key with default size
|
||||
key, err := GenerateKey(encryption.AES256KeySize)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, key, encryption.AES256KeySize)
|
||||
|
||||
// Test generating key with custom size
|
||||
customSize := 64
|
||||
key, err = GenerateKey(customSize)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, key, customSize)
|
||||
|
||||
// Test generating key with size smaller than minimum (should use minimum)
|
||||
key, err = GenerateKey(16)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, key, encryption.MinKeyLength)
|
||||
}
|
||||
|
||||
func TestGenerateKeyString(t *testing.T) {
|
||||
// Test generating key string
|
||||
keyStr, err := GenerateKeyString(encryption.AES256KeySize)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, keyStr)
|
||||
}
|
||||
|
||||
func TestValidateKeyLength(t *testing.T) {
|
||||
// Test valid key length
|
||||
key := make([]byte, encryption.MinKeyLength)
|
||||
assert.True(t, ValidateKeyLength(key))
|
||||
|
||||
// Test invalid key length
|
||||
key = make([]byte, encryption.MinKeyLength-1)
|
||||
assert.False(t, ValidateKeyLength(key))
|
||||
}
|
||||
|
||||
func TestKeyManager_Initialize(t *testing.T) {
|
||||
// Setup test environment
|
||||
testEnvVar := "TEST_ENCRYPTION_KEY"
|
||||
validKey, err := GenerateKey(encryption.AES256KeySize)
|
||||
require.NoError(t, err)
|
||||
validKeyBase64 := encodeToBase64(validKey)
|
||||
|
||||
t.Run("Valid key in environment", func(t *testing.T) {
|
||||
// Set a valid key in environment
|
||||
os.Setenv(testEnvVar, validKeyBase64)
|
||||
defer os.Unsetenv(testEnvVar)
|
||||
|
||||
km := NewKeyManager(testEnvVar)
|
||||
err := km.Initialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that key is properly stored
|
||||
key, err := km.GetPrimaryKey()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, validKey, key)
|
||||
})
|
||||
|
||||
t.Run("Missing key in environment", func(t *testing.T) {
|
||||
os.Unsetenv(testEnvVar)
|
||||
|
||||
km := NewKeyManager(testEnvVar)
|
||||
err := km.Initialize()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "encryption key not provided")
|
||||
})
|
||||
|
||||
t.Run("Invalid key format", func(t *testing.T) {
|
||||
os.Setenv(testEnvVar, "not-a-valid-base64-or-hex-key")
|
||||
defer os.Unsetenv(testEnvVar)
|
||||
|
||||
km := NewKeyManager(testEnvVar)
|
||||
err := km.Initialize()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "invalid")
|
||||
})
|
||||
|
||||
t.Run("Key too short", func(t *testing.T) {
|
||||
shortKey := make([]byte, encryption.MinKeyLength-1)
|
||||
os.Setenv(testEnvVar, encodeToBase64(shortKey))
|
||||
defer os.Unsetenv(testEnvVar)
|
||||
|
||||
km := NewKeyManager(testEnvVar)
|
||||
err := km.Initialize()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "too short")
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyManager_StoreKeyEnvironment(t *testing.T) {
|
||||
testEnvVar := "TEST_STORE_KEY"
|
||||
km := NewKeyManager(testEnvVar)
|
||||
|
||||
// Generate a valid key
|
||||
key, err := GenerateKey(encryption.AES256KeySize)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Store the key
|
||||
err = km.StoreKeyEnvironment(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify key is stored in environment
|
||||
envValue := os.Getenv(testEnvVar)
|
||||
assert.NotEmpty(t, envValue)
|
||||
|
||||
// Verify key is stored in KeyManager
|
||||
storedKey, err := km.GetPrimaryKey()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, key, storedKey)
|
||||
|
||||
// Clean up
|
||||
os.Unsetenv(testEnvVar)
|
||||
}
|
||||
|
||||
func TestKeyManager_GetPrimaryKey_NotInitialized(t *testing.T) {
|
||||
km := NewKeyManager("NONEXISTENT_KEY")
|
||||
key, err := km.GetPrimaryKey()
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, key)
|
||||
assert.Contains(t, err.Error(), "not initialized")
|
||||
}
|
||||
|
||||
// Helper function to encode bytes to base64
|
||||
func encodeToBase64(data []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(data)
|
||||
}
|
||||
Reference in New Issue
Block a user