Files
GoMFT/internal/encryption/audit/security_test_framework.go
StarFleetCPTN 31871bd16e 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.
2025-04-16 17:18:53 -07:00

564 lines
17 KiB
Go

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
}