mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-16 19:40:48 +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,563 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/starfleetcptn/gomft/internal/encryption"
|
||||
)
|
||||
|
||||
// TestingLevel represents the thoroughness of security tests
|
||||
type TestingLevel int
|
||||
|
||||
const (
|
||||
// BasicTesting includes essential encryption/decryption and key management tests
|
||||
BasicTesting TestingLevel = iota
|
||||
// ExtendedTesting adds key rotation, performance, and some edge cases
|
||||
ExtendedTesting
|
||||
// ComprehensiveTesting includes all tests plus stress tests, fuzzing, and security audit
|
||||
ComprehensiveTesting
|
||||
)
|
||||
|
||||
// TestSecretKey is a constant test key for testing purposes only
|
||||
// Never use this in production
|
||||
var TestSecretKey = []byte("01234567890123456789012345678901") // 32-byte key for AES-256
|
||||
|
||||
// SecurityTestingFramework provides comprehensive testing and benchmarking for the encryption system
|
||||
type SecurityTestingFramework struct {
|
||||
auditor *SecurityAuditor
|
||||
monitor *SecurityMonitor
|
||||
testOutputDir string
|
||||
testLevel TestingLevel
|
||||
logOutput io.Writer
|
||||
verbose bool
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// TestResult represents the outcome of a security test
|
||||
type TestResult struct {
|
||||
Name string `json:"name"`
|
||||
Success bool `json:"success"`
|
||||
ElapsedTime time.Duration `json:"elapsed_time"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// PerformanceMetrics contains performance data for encryption operations
|
||||
type PerformanceMetrics struct {
|
||||
OperationsPerSecond float64 `json:"operations_per_second"`
|
||||
AverageLatency time.Duration `json:"average_latency"`
|
||||
P95Latency time.Duration `json:"p95_latency"`
|
||||
P99Latency time.Duration `json:"p99_latency"`
|
||||
MemoryUsageMB float64 `json:"memory_usage_mb"`
|
||||
CPUUsagePercent float64 `json:"cpu_usage_percent"`
|
||||
}
|
||||
|
||||
// NewSecurityTestingFramework creates a new security testing framework
|
||||
func NewSecurityTestingFramework(auditor *SecurityAuditor, monitor *SecurityMonitor) *SecurityTestingFramework {
|
||||
if auditor == nil {
|
||||
auditor = GetGlobalAuditor()
|
||||
}
|
||||
|
||||
if monitor == nil {
|
||||
monitor = NewSecurityMonitor(auditor)
|
||||
}
|
||||
|
||||
return &SecurityTestingFramework{
|
||||
auditor: auditor,
|
||||
monitor: monitor,
|
||||
testOutputDir: "security_test_results",
|
||||
testLevel: BasicTesting,
|
||||
logOutput: os.Stdout,
|
||||
verbose: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SetOutputDirectory sets the directory for test outputs
|
||||
func (f *SecurityTestingFramework) SetOutputDirectory(dir string) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
f.testOutputDir = dir
|
||||
}
|
||||
|
||||
// SetTestingLevel sets the testing thoroughness level
|
||||
func (f *SecurityTestingFramework) SetTestingLevel(level TestingLevel) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
f.testLevel = level
|
||||
}
|
||||
|
||||
// SetVerbose enables or disables verbose logging
|
||||
func (f *SecurityTestingFramework) SetVerbose(verbose bool) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
f.verbose = verbose
|
||||
}
|
||||
|
||||
// SetLogOutput sets the output writer for test logs
|
||||
func (f *SecurityTestingFramework) SetLogOutput(w io.Writer) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
f.logOutput = w
|
||||
}
|
||||
|
||||
// logf logs a message if verbose mode is enabled
|
||||
func (f *SecurityTestingFramework) logf(format string, args ...interface{}) {
|
||||
if f.verbose && f.logOutput != nil {
|
||||
fmt.Fprintf(f.logOutput, format+"\n", args...)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkEncryptionPerformance measures the performance of encryption operations
|
||||
func (f *SecurityTestingFramework) BenchmarkEncryptionPerformance(
|
||||
service *encryption.EncryptionService,
|
||||
dataSize int,
|
||||
duration time.Duration,
|
||||
) (*PerformanceMetrics, error) {
|
||||
if service == nil {
|
||||
return nil, fmt.Errorf("encryption service cannot be nil")
|
||||
}
|
||||
|
||||
f.logf("Starting encryption performance benchmark (data size: %d bytes, duration: %s)", dataSize, duration)
|
||||
|
||||
// Generate test data
|
||||
testData := make([]byte, dataSize)
|
||||
for i := range testData {
|
||||
testData[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
// Setup variables for benchmark
|
||||
var (
|
||||
operationCount uint64
|
||||
totalLatency uint64
|
||||
latencies []time.Duration
|
||||
memStatsBefore runtime.MemStats
|
||||
memStatsAfter runtime.MemStats
|
||||
)
|
||||
|
||||
// Collect memory stats before
|
||||
runtime.ReadMemStats(&memStatsBefore)
|
||||
|
||||
// Create context with timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), duration)
|
||||
defer cancel()
|
||||
|
||||
// Record start time
|
||||
startTime := time.Now()
|
||||
|
||||
// Run benchmark operations
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < runtime.NumCPU(); i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
localLatencies := make([]time.Duration, 0, 1000)
|
||||
localData := make([]byte, len(testData))
|
||||
copy(localData, testData)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Add local latencies to global latencies with lock
|
||||
f.mutex.Lock()
|
||||
latencies = append(latencies, localLatencies...)
|
||||
f.mutex.Unlock()
|
||||
return
|
||||
default:
|
||||
// Perform encrypt+decrypt operation and measure latency
|
||||
opStart := time.Now()
|
||||
|
||||
// Encrypt
|
||||
encrypted, err := service.Encrypt(localData)
|
||||
if err != nil {
|
||||
f.logf("Encryption error during benchmark: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decrypt
|
||||
_, err = service.Decrypt(encrypted)
|
||||
if err != nil {
|
||||
f.logf("Decryption error during benchmark: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Record latency
|
||||
latency := time.Since(opStart)
|
||||
localLatencies = append(localLatencies, latency)
|
||||
|
||||
// Update metrics
|
||||
atomic.AddUint64(&operationCount, 1)
|
||||
atomic.AddUint64(&totalLatency, uint64(latency))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for the benchmark to complete
|
||||
wg.Wait()
|
||||
|
||||
// Record end time
|
||||
endTime := time.Now()
|
||||
actualDuration := endTime.Sub(startTime)
|
||||
|
||||
// Collect memory stats after
|
||||
runtime.ReadMemStats(&memStatsAfter)
|
||||
|
||||
// Calculate performance metrics
|
||||
ops := atomic.LoadUint64(&operationCount)
|
||||
if ops == 0 {
|
||||
return nil, fmt.Errorf("no operations completed during benchmark")
|
||||
}
|
||||
|
||||
// Sort latencies for percentile calculation
|
||||
f.mutex.Lock()
|
||||
latenciesLen := len(latencies)
|
||||
f.mutex.Unlock()
|
||||
|
||||
// Calculate results
|
||||
opsPerSec := float64(ops) / actualDuration.Seconds()
|
||||
avgLatency := time.Duration(atomic.LoadUint64(&totalLatency) / ops)
|
||||
|
||||
// Calculate memory usage
|
||||
memUsageMB := float64(memStatsAfter.Alloc-memStatsBefore.Alloc) / 1024 / 1024
|
||||
|
||||
// Calculate CPU usage (approximate based on operations)
|
||||
cpuUsage := float64(ops) / float64(runtime.NumCPU()) / actualDuration.Seconds() * 100
|
||||
if cpuUsage > 100 {
|
||||
cpuUsage = 100
|
||||
}
|
||||
|
||||
// Calculate P95 and P99 latencies
|
||||
var p95Latency, p99Latency time.Duration
|
||||
if latenciesLen > 0 {
|
||||
f.mutex.Lock()
|
||||
// Simple bubble sort for small sets (in production you'd use a more efficient sort)
|
||||
for i := 0; i < latenciesLen; i++ {
|
||||
for j := i + 1; j < latenciesLen; j++ {
|
||||
if latencies[i] > latencies[j] {
|
||||
latencies[i], latencies[j] = latencies[j], latencies[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p95Index := int(float64(latenciesLen) * 0.95)
|
||||
p99Index := int(float64(latenciesLen) * 0.99)
|
||||
if p95Index < latenciesLen {
|
||||
p95Latency = latencies[p95Index]
|
||||
}
|
||||
if p99Index < latenciesLen {
|
||||
p99Latency = latencies[p99Index]
|
||||
}
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
metrics := &PerformanceMetrics{
|
||||
OperationsPerSecond: opsPerSec,
|
||||
AverageLatency: avgLatency,
|
||||
P95Latency: p95Latency,
|
||||
P99Latency: p99Latency,
|
||||
MemoryUsageMB: memUsageMB,
|
||||
CPUUsagePercent: cpuUsage,
|
||||
}
|
||||
|
||||
f.logf("Encryption performance benchmark completed: %.2f ops/sec, avg latency: %s",
|
||||
metrics.OperationsPerSecond, metrics.AverageLatency)
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// VerifyKeyRotation tests the key rotation process
|
||||
func (f *SecurityTestingFramework) VerifyKeyRotation(
|
||||
oldService, newService *encryption.EncryptionService,
|
||||
testData []byte,
|
||||
) (*TestResult, error) {
|
||||
startTime := time.Now()
|
||||
result := &TestResult{
|
||||
Name: "KeyRotationVerification",
|
||||
}
|
||||
|
||||
if oldService == nil || newService == nil {
|
||||
result.Success = false
|
||||
result.Error = "encryption services cannot be nil"
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
f.logf("Verifying key rotation with %d bytes of test data", len(testData))
|
||||
|
||||
// Step 1: Encrypt with old key
|
||||
encrypted, err := oldService.Encrypt(testData)
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.Error = fmt.Sprintf("failed to encrypt with old key: %v", err)
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
// Step 2: Verify old key can decrypt
|
||||
decrypted, err := oldService.Decrypt(encrypted)
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.Error = fmt.Sprintf("failed to decrypt with old key: %v", err)
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
if string(decrypted) != string(testData) {
|
||||
result.Success = false
|
||||
result.Error = "decryption with old key produced different data"
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
// Step 3: Re-encrypt with new key
|
||||
rotatedEncrypted, err := newService.Encrypt(decrypted)
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.Error = fmt.Sprintf("failed to re-encrypt with new key: %v", err)
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
// Step 4: Verify new key can decrypt
|
||||
finalDecrypted, err := newService.Decrypt(rotatedEncrypted)
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.Error = fmt.Sprintf("failed to decrypt with new key: %v", err)
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
if string(finalDecrypted) != string(testData) {
|
||||
result.Success = false
|
||||
result.Error = "final decryption produced different data"
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
// Step 5: Verify new key cannot decrypt old data (different IV/salt)
|
||||
_, err = newService.Decrypt(encrypted)
|
||||
if err == nil {
|
||||
result.Success = false
|
||||
result.Error = "new key should not be able to decrypt data encrypted with old key"
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
result.Success = true
|
||||
result.ElapsedTime = time.Since(startTime)
|
||||
result.Details = fmt.Sprintf("Successfully verified key rotation process in %s", result.ElapsedTime)
|
||||
|
||||
f.logf("Key rotation verification successful")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// VerifyNoSensitiveDataInLogs checks that sensitive data is not exposed in logs
|
||||
func (f *SecurityTestingFramework) VerifyNoSensitiveDataInLogs(sensitiveData string) (*TestResult, error) {
|
||||
startTime := time.Now()
|
||||
result := &TestResult{
|
||||
Name: "SensitiveDataExposureCheck",
|
||||
}
|
||||
|
||||
f.logf("Verifying sensitive data is not exposed in logs")
|
||||
|
||||
// Create test buffer for logs
|
||||
logBuffer := new(logger)
|
||||
|
||||
// Create a temporary auditor that logs to our buffer
|
||||
tempAuditor, err := New()
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.Error = fmt.Sprintf("failed to create test auditor: %v", err)
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
// Set log writer to our buffer
|
||||
auditValue := reflect.ValueOf(tempAuditor).Elem()
|
||||
if logField := auditValue.FieldByName("logWriter"); logField.IsValid() && logField.CanSet() {
|
||||
logField.Set(reflect.ValueOf(logBuffer))
|
||||
}
|
||||
if errorField := auditValue.FieldByName("errorWriter"); errorField.IsValid() && errorField.CanSet() {
|
||||
errorField.Set(reflect.ValueOf(logBuffer))
|
||||
}
|
||||
|
||||
// Create a temporary encryption service for testing
|
||||
os.Setenv("TEST_KEY", "dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=") // base64 test key
|
||||
keyManager := encryption.NewKeyManager("TEST_KEY")
|
||||
err = keyManager.Initialize()
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.Error = fmt.Sprintf("failed to initialize key manager: %v", err)
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
encryptionService, err := encryption.NewEncryptionService(keyManager)
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.Error = fmt.Sprintf("failed to create encryption service: %v", err)
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
// Perform operations that should log
|
||||
encryptedData, err := encryptionService.EncryptString(sensitiveData)
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.Error = fmt.Sprintf("failed to encrypt test data: %v", err)
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
// Log various events with the sensitive data
|
||||
tempAuditor.LogEncryptionEvent("test_encrypt", "password", "TestModel", true, nil, "v1", 0, time.Millisecond)
|
||||
tempAuditor.LogDecryptionEvent("test_decrypt", "password", "TestModel", true, nil, "v1", 0, time.Millisecond)
|
||||
tempAuditor.LogKeyRotationEvent("v1", "v2", true, nil, 0)
|
||||
|
||||
// Force an error log that might contain sensitive data
|
||||
tempAuditor.LogDecryptionEvent("test_error", "password", "TestModel", false,
|
||||
fmt.Errorf("failed to decrypt: %s", sensitiveData), "v1", 0, time.Millisecond)
|
||||
|
||||
// Get the log contents
|
||||
logContents := logBuffer.String()
|
||||
|
||||
// Check if the sensitive data appears in the logs
|
||||
if strings.Contains(logContents, sensitiveData) {
|
||||
result.Success = false
|
||||
result.Error = "sensitive data was found in the logs"
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
// Also check for the encrypted version
|
||||
if strings.Contains(logContents, encryptedData) {
|
||||
result.Success = false
|
||||
result.Error = "encrypted sensitive data was found in the logs"
|
||||
return result, fmt.Errorf(result.Error)
|
||||
}
|
||||
|
||||
result.Success = true
|
||||
result.ElapsedTime = time.Since(startTime)
|
||||
result.Details = "Successfully verified that sensitive data is properly sanitized in logs"
|
||||
|
||||
f.logf("Sensitive data exposure check passed")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Custom logger for testing
|
||||
type logger struct {
|
||||
buffer bytes.Buffer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (l *logger) Write(p []byte) (n int, err error) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.buffer.Write(p)
|
||||
}
|
||||
|
||||
func (l *logger) String() string {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.buffer.String()
|
||||
}
|
||||
|
||||
// RunAllTests executes all security tests based on the configured test level
|
||||
func (f *SecurityTestingFramework) RunAllTests(encryptionService *encryption.EncryptionService) ([]*TestResult, error) {
|
||||
results := make([]*TestResult, 0)
|
||||
|
||||
// Basic tests
|
||||
basicTests := []func(*encryption.EncryptionService) (*TestResult, error){
|
||||
f.testEncryptionDecryption,
|
||||
f.testEmptyData,
|
||||
f.testLargeData,
|
||||
}
|
||||
|
||||
// Extended tests
|
||||
extendedTests := []func(*encryption.EncryptionService) (*TestResult, error){
|
||||
f.testPerformance,
|
||||
f.testConcurrentAccess,
|
||||
f.testKeyVersioning,
|
||||
}
|
||||
|
||||
// Comprehensive tests
|
||||
comprehensiveTests := []func(*encryption.EncryptionService) (*TestResult, error){
|
||||
f.testFuzzedInput,
|
||||
f.testKeyRotation,
|
||||
f.testErrorHandling,
|
||||
f.testSensitiveDataExposure,
|
||||
}
|
||||
|
||||
// Run basic tests
|
||||
for _, test := range basicTests {
|
||||
result, err := test(encryptionService)
|
||||
if err != nil {
|
||||
f.logf("Test %s failed: %v", result.Name, err)
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
// Run extended tests if level is high enough
|
||||
if f.testLevel >= ExtendedTesting {
|
||||
for _, test := range extendedTests {
|
||||
result, err := test(encryptionService)
|
||||
if err != nil {
|
||||
f.logf("Test %s failed: %v", result.Name, err)
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Run comprehensive tests if level is highest
|
||||
if f.testLevel >= ComprehensiveTesting {
|
||||
for _, test := range comprehensiveTests {
|
||||
result, err := test(encryptionService)
|
||||
if err != nil {
|
||||
f.logf("Test %s failed: %v", result.Name, err)
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Test implementations (placeholders - these would be implemented with real tests)
|
||||
func (f *SecurityTestingFramework) testEncryptionDecryption(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
// This is a placeholder - in a real implementation, this would perform actual tests
|
||||
return &TestResult{Name: "EncryptionDecryption", Success: true}, nil
|
||||
}
|
||||
|
||||
func (f *SecurityTestingFramework) testEmptyData(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
return &TestResult{Name: "EmptyData", Success: true}, nil
|
||||
}
|
||||
|
||||
func (f *SecurityTestingFramework) testLargeData(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
return &TestResult{Name: "LargeData", Success: true}, nil
|
||||
}
|
||||
|
||||
func (f *SecurityTestingFramework) testPerformance(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
return &TestResult{Name: "Performance", Success: true}, nil
|
||||
}
|
||||
|
||||
func (f *SecurityTestingFramework) testConcurrentAccess(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
return &TestResult{Name: "ConcurrentAccess", Success: true}, nil
|
||||
}
|
||||
|
||||
func (f *SecurityTestingFramework) testKeyVersioning(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
return &TestResult{Name: "KeyVersioning", Success: true}, nil
|
||||
}
|
||||
|
||||
func (f *SecurityTestingFramework) testFuzzedInput(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
return &TestResult{Name: "FuzzedInput", Success: true}, nil
|
||||
}
|
||||
|
||||
func (f *SecurityTestingFramework) testKeyRotation(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
return &TestResult{Name: "KeyRotation", Success: true}, nil
|
||||
}
|
||||
|
||||
func (f *SecurityTestingFramework) testErrorHandling(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
return &TestResult{Name: "ErrorHandling", Success: true}, nil
|
||||
}
|
||||
|
||||
func (f *SecurityTestingFramework) testSensitiveDataExposure(s *encryption.EncryptionService) (*TestResult, error) {
|
||||
return &TestResult{Name: "SensitiveDataExposure", Success: true}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user