564 lines
15 KiB
Go
564 lines
15 KiB
Go
package crypto
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"chorus/pkg/config"
|
|
)
|
|
|
|
// TestSecurityConfig tests SecurityConfig enforcement
|
|
func TestSecurityConfig(t *testing.T) {
|
|
// Create temporary audit log file
|
|
tmpDir, err := ioutil.TempDir("", "chorus_security_test")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// Test cases for security configuration
|
|
testCases := []struct {
|
|
name string
|
|
keyRotationDays int
|
|
auditLogging bool
|
|
expectWarnings int
|
|
expectRotationJobs bool
|
|
}{
|
|
{
|
|
name: "audit_logging_disabled",
|
|
keyRotationDays: 90,
|
|
auditLogging: false,
|
|
expectWarnings: 1, // Warning for disabled audit logging
|
|
expectRotationJobs: true,
|
|
},
|
|
{
|
|
name: "key_rotation_disabled",
|
|
keyRotationDays: 0,
|
|
auditLogging: true,
|
|
expectWarnings: 1, // Warning for disabled key rotation
|
|
expectRotationJobs: false,
|
|
},
|
|
{
|
|
name: "security_fully_enabled",
|
|
keyRotationDays: 30,
|
|
auditLogging: true,
|
|
expectWarnings: 0,
|
|
expectRotationJobs: true,
|
|
},
|
|
{
|
|
name: "both_security_features_disabled",
|
|
keyRotationDays: 0,
|
|
auditLogging: false,
|
|
expectWarnings: 2, // Warnings for both disabled features
|
|
expectRotationJobs: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Create test configuration
|
|
cfg := &config.Config{
|
|
Agent: config.AgentConfig{
|
|
ID: "test-agent",
|
|
},
|
|
Security: config.SecurityConfig{
|
|
KeyRotationDays: tc.keyRotationDays,
|
|
AuditLogging: tc.auditLogging,
|
|
AuditPath: fmt.Sprintf("%s/audit-%s.log", tmpDir, tc.name),
|
|
},
|
|
}
|
|
|
|
// Create mock audit logger
|
|
mockLogger := &MockAuditLogger{events: make([]*SecurityEvent, 0)}
|
|
|
|
// Create mock key store
|
|
mockKeyStore := &MockKeyStore{
|
|
keys: make(map[string]*SecureKeyData),
|
|
}
|
|
|
|
// Create key manager
|
|
km, err := NewKeyManager(cfg, mockKeyStore, mockLogger)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create key manager: %v", err)
|
|
}
|
|
defer func() {
|
|
if km.rotationScheduler.running {
|
|
km.rotationScheduler.Stop()
|
|
}
|
|
}()
|
|
|
|
// Give the key manager time to initialize
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Check audit logger for expected warnings
|
|
securityWarnings := 0
|
|
for _, event := range mockLogger.events {
|
|
if event.EventType == "security_warning" {
|
|
securityWarnings++
|
|
}
|
|
}
|
|
|
|
if securityWarnings != tc.expectWarnings {
|
|
t.Errorf("Expected %d security warnings, got %d", tc.expectWarnings, securityWarnings)
|
|
}
|
|
|
|
// Check if rotation scheduler is running
|
|
isRunning := km.rotationScheduler.running
|
|
if tc.expectRotationJobs && !isRunning {
|
|
t.Errorf("Expected rotation scheduler to be running")
|
|
} else if !tc.expectRotationJobs && isRunning {
|
|
t.Errorf("Expected rotation scheduler to not be running")
|
|
}
|
|
|
|
// Test key rotation monitoring
|
|
if tc.keyRotationDays > 0 {
|
|
testKeyRotationMonitoring(t, km, mockKeyStore, mockLogger)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// testKeyRotationMonitoring tests the key rotation monitoring functionality
|
|
func testKeyRotationMonitoring(t *testing.T, km *KeyManager, keyStore *MockKeyStore, mockLogger *MockAuditLogger) {
|
|
// Create an old key that should trigger rotation warning
|
|
oldKey := &SecureKeyData{
|
|
KeyID: "old-test-key",
|
|
KeyType: "age-x25519",
|
|
CreatedAt: time.Now().Add(-100 * 24 * time.Hour), // 100 days old
|
|
Status: KeyStatusActive,
|
|
}
|
|
keyStore.keys[oldKey.KeyID] = oldKey
|
|
|
|
// Create metadata for the old key
|
|
oldKeyMeta := &KeyMetadata{
|
|
KeyID: "old-test-key",
|
|
KeyType: "age-x25519",
|
|
RoleID: "test-role",
|
|
CreatedAt: time.Now().Add(-100 * 24 * time.Hour),
|
|
Status: KeyStatusActive,
|
|
}
|
|
keyStore.metadata = append(keyStore.metadata, oldKeyMeta)
|
|
|
|
// Run key rotation check
|
|
km.checkKeysForRotation()
|
|
|
|
// Give time for async operations
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Check if rotation warning was logged
|
|
rotationWarnings := 0
|
|
for _, event := range mockLogger.keyRotationEvents {
|
|
if event.Reason == "key_rotation_overdue" {
|
|
rotationWarnings++
|
|
}
|
|
}
|
|
|
|
if rotationWarnings == 0 {
|
|
t.Errorf("Expected at least one key rotation warning for overdue key")
|
|
}
|
|
}
|
|
|
|
// TestDHTSecurityIntegration tests DHT security integration
|
|
func TestDHTSecurityIntegration(t *testing.T) {
|
|
// Create test configuration
|
|
cfg := &config.Config{
|
|
Agent: config.AgentConfig{
|
|
ID: "test-agent",
|
|
Role: "backend_developer",
|
|
},
|
|
Security: config.SecurityConfig{
|
|
KeyRotationDays: 90,
|
|
AuditLogging: true,
|
|
AuditPath: "/tmp/test-audit.log",
|
|
},
|
|
}
|
|
|
|
// Create mock DHT storage (simplified for testing)
|
|
ctx := context.Background()
|
|
|
|
// Test role-based access policies
|
|
testCases := []struct {
|
|
name string
|
|
currentRole string
|
|
operation string
|
|
shouldAllow bool
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "admin_can_store",
|
|
currentRole: "admin",
|
|
operation: "store",
|
|
shouldAllow: true,
|
|
},
|
|
{
|
|
name: "backend_developer_can_store",
|
|
currentRole: "backend_developer",
|
|
operation: "store",
|
|
shouldAllow: true,
|
|
},
|
|
{
|
|
name: "readonly_cannot_store",
|
|
currentRole: "readonly_user",
|
|
operation: "store",
|
|
shouldAllow: false,
|
|
expectedError: "read-only authority",
|
|
},
|
|
{
|
|
name: "all_roles_can_retrieve",
|
|
currentRole: "qa_engineer",
|
|
operation: "retrieve",
|
|
shouldAllow: true,
|
|
},
|
|
{
|
|
name: "suggestion_role_cannot_announce",
|
|
currentRole: "suggestion_role",
|
|
operation: "announce",
|
|
shouldAllow: false,
|
|
expectedError: "lacks authority",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Set role in config
|
|
cfg.Agent.Role = tc.currentRole
|
|
|
|
// Test the specific access policy check
|
|
var err error
|
|
switch tc.operation {
|
|
case "store":
|
|
err = checkStoreAccessPolicyTest(tc.currentRole)
|
|
case "retrieve":
|
|
err = checkRetrieveAccessPolicyTest(tc.currentRole)
|
|
case "announce":
|
|
err = checkAnnounceAccessPolicyTest(tc.currentRole)
|
|
}
|
|
|
|
if tc.shouldAllow {
|
|
if err != nil {
|
|
t.Errorf("Expected operation to be allowed but got error: %v", err)
|
|
}
|
|
} else {
|
|
if err == nil {
|
|
t.Errorf("Expected operation to be denied but it was allowed")
|
|
} else if tc.expectedError != "" && err.Error() != tc.expectedError {
|
|
// Check if error message contains expected substring
|
|
if len(tc.expectedError) > 0 && !containsSubstring(err.Error(), tc.expectedError) {
|
|
t.Errorf("Expected error to contain '%s', got '%s'", tc.expectedError, err.Error())
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAuditLogging tests comprehensive audit logging
|
|
func TestAuditLogging(t *testing.T) {
|
|
tmpDir, err := ioutil.TempDir("", "chorus_audit_test")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// Test audit logging for different operations
|
|
testOperations := []struct {
|
|
operation string
|
|
ucxlAddress string
|
|
role string
|
|
success bool
|
|
errorMsg string
|
|
}{
|
|
{"store", "agent1:backend_developer:project1:task1", "backend_developer", true, ""},
|
|
{"store", "agent2:invalid_role:project2:task2", "invalid_role", false, "unknown role"},
|
|
{"retrieve", "agent1:backend_developer:project1:task1", "frontend_developer", true, ""},
|
|
{"announce", "agent1:backend_developer:project1:task1", "senior_software_architect", true, ""},
|
|
{"announce", "agent2:readonly:project2:task2", "readonly_user", false, "lacks authority"},
|
|
}
|
|
|
|
for _, op := range testOperations {
|
|
t.Run(fmt.Sprintf("%s_%s_%v", op.operation, op.role, op.success), func(t *testing.T) {
|
|
// Create configuration with audit logging enabled
|
|
cfg := &config.Config{
|
|
Agent: config.AgentConfig{
|
|
ID: "test-agent",
|
|
Role: op.role,
|
|
},
|
|
Security: config.SecurityConfig{
|
|
KeyRotationDays: 90,
|
|
AuditLogging: true,
|
|
AuditPath: fmt.Sprintf("%s/audit-%s.log", tmpDir, op.operation),
|
|
},
|
|
}
|
|
|
|
// Simulate audit logging for the operation
|
|
auditResult := simulateAuditOperation(cfg, op.operation, op.ucxlAddress, op.role, op.success, op.errorMsg)
|
|
|
|
// Validate audit log entry
|
|
if auditResult == nil {
|
|
t.Errorf("Expected audit log entry but got nil")
|
|
return
|
|
}
|
|
|
|
if auditResult["operation"] != op.operation {
|
|
t.Errorf("Expected operation '%s', got '%s'", op.operation, auditResult["operation"])
|
|
}
|
|
|
|
if auditResult["role"] != op.role {
|
|
t.Errorf("Expected role '%s', got '%s'", op.role, auditResult["role"])
|
|
}
|
|
|
|
if auditResult["success"] != op.success {
|
|
t.Errorf("Expected success %v, got %v", op.success, auditResult["success"])
|
|
}
|
|
|
|
// Check for audit trail
|
|
if auditTrail, ok := auditResult["audit_trail"].(string); !ok || auditTrail == "" {
|
|
t.Errorf("Expected non-empty audit trail")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestKeyRotationScheduling tests key rotation scheduling
|
|
func TestKeyRotationScheduling(t *testing.T) {
|
|
cfg := &config.Config{
|
|
Agent: config.AgentConfig{
|
|
ID: "test-agent",
|
|
},
|
|
Security: config.SecurityConfig{
|
|
KeyRotationDays: 7, // Short rotation for testing
|
|
AuditLogging: true,
|
|
AuditPath: "/tmp/test-rotation-audit.log",
|
|
},
|
|
}
|
|
|
|
mockLogger := &MockAuditLogger{events: make([]*SecurityEvent, 0)}
|
|
mockKeyStore := &MockKeyStore{keys: make(map[string]*SecureKeyData)}
|
|
|
|
km, err := NewKeyManager(cfg, mockKeyStore, mockLogger)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create key manager: %v", err)
|
|
}
|
|
defer func() {
|
|
if km.rotationScheduler.running {
|
|
km.rotationScheduler.Stop()
|
|
}
|
|
}()
|
|
|
|
// Test that rotation jobs are scheduled for all roles
|
|
roles := config.GetPredefinedRoles()
|
|
expectedJobs := len(roles)
|
|
|
|
if len(km.rotationScheduler.scheduledJobs) != expectedJobs {
|
|
t.Errorf("Expected %d rotation jobs, got %d", expectedJobs, len(km.rotationScheduler.scheduledJobs))
|
|
}
|
|
|
|
// Test rotation policy is correctly set
|
|
for _, job := range km.rotationScheduler.scheduledJobs {
|
|
if job.Policy.RotationInterval != 7*24*time.Hour {
|
|
t.Errorf("Expected rotation interval of 7 days, got %v", job.Policy.RotationInterval)
|
|
}
|
|
if !job.Policy.AutoRotate {
|
|
t.Errorf("Expected auto-rotate to be enabled")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mock implementations for testing
|
|
|
|
type MockAuditLogger struct {
|
|
events []*SecurityEvent
|
|
keyRotationEvents []*KeyRotationEvent
|
|
}
|
|
|
|
func (m *MockAuditLogger) LogAccess(entry *AccessLogEntry) error {
|
|
// Implementation for testing
|
|
return nil
|
|
}
|
|
|
|
func (m *MockAuditLogger) LogKeyRotation(event *KeyRotationEvent) error {
|
|
m.keyRotationEvents = append(m.keyRotationEvents, event)
|
|
return nil
|
|
}
|
|
|
|
func (m *MockAuditLogger) LogSecurityEvent(event *SecurityEvent) error {
|
|
m.events = append(m.events, event)
|
|
return nil
|
|
}
|
|
|
|
func (m *MockAuditLogger) GetAuditTrail(criteria *AuditCriteria) ([]*AuditEvent, error) {
|
|
return []*AuditEvent{}, nil
|
|
}
|
|
|
|
type MockKeyStore struct {
|
|
keys map[string]*SecureKeyData
|
|
metadata []*KeyMetadata
|
|
}
|
|
|
|
func (m *MockKeyStore) StoreKey(keyID string, keyData *SecureKeyData) error {
|
|
m.keys[keyID] = keyData
|
|
return nil
|
|
}
|
|
|
|
func (m *MockKeyStore) RetrieveKey(keyID string) (*SecureKeyData, error) {
|
|
if key, exists := m.keys[keyID]; exists {
|
|
return key, nil
|
|
}
|
|
return nil, fmt.Errorf("key not found: %s", keyID)
|
|
}
|
|
|
|
func (m *MockKeyStore) DeleteKey(keyID string) error {
|
|
delete(m.keys, keyID)
|
|
return nil
|
|
}
|
|
|
|
func (m *MockKeyStore) ListKeys(filter *KeyFilter) ([]*KeyMetadata, error) {
|
|
return m.metadata, nil
|
|
}
|
|
|
|
func (m *MockKeyStore) BackupKeys(criteria *BackupCriteria) (*KeyBackup, error) {
|
|
return &KeyBackup{}, nil
|
|
}
|
|
|
|
func (m *MockKeyStore) RestoreKeys(backup *KeyBackup) error {
|
|
return nil
|
|
}
|
|
|
|
// Test helper functions
|
|
|
|
func checkStoreAccessPolicyTest(role string) error {
|
|
roles := config.GetPredefinedRoles()
|
|
if _, exists := roles[role]; !exists {
|
|
return fmt.Errorf("unknown creator role: %s", role)
|
|
}
|
|
|
|
roleData := roles[role]
|
|
if roleData.AuthorityLevel == config.AuthorityReadOnly {
|
|
return fmt.Errorf("role %s has read-only authority and cannot store content", role)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkRetrieveAccessPolicyTest(role string) error {
|
|
roles := config.GetPredefinedRoles()
|
|
if _, exists := roles[role]; !exists {
|
|
return fmt.Errorf("unknown current role: %s", role)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkAnnounceAccessPolicyTest(role string) error {
|
|
roles := config.GetPredefinedRoles()
|
|
if _, exists := roles[role]; !exists {
|
|
return fmt.Errorf("unknown current role: %s", role)
|
|
}
|
|
|
|
roleData := roles[role]
|
|
if roleData.AuthorityLevel == config.AuthorityReadOnly || roleData.AuthorityLevel == config.AuthoritySuggestion {
|
|
return fmt.Errorf("role %s lacks authority to announce content", role)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func simulateAuditOperation(cfg *config.Config, operation, ucxlAddress, role string, success bool, errorMsg string) map[string]interface{} {
|
|
if !cfg.Security.AuditLogging || cfg.Security.AuditPath == "" {
|
|
return nil
|
|
}
|
|
|
|
auditEntry := map[string]interface{}{
|
|
"timestamp": time.Now(),
|
|
"operation": operation,
|
|
"node_id": "test-node",
|
|
"ucxl_address": ucxlAddress,
|
|
"role": role,
|
|
"success": success,
|
|
"error_message": errorMsg,
|
|
"audit_trail": fmt.Sprintf("DHT-%s-%s-%d", operation, ucxlAddress, time.Now().Unix()),
|
|
}
|
|
|
|
return auditEntry
|
|
}
|
|
|
|
func containsSubstring(str, substr string) bool {
|
|
return len(substr) > 0 && len(str) >= len(substr) &&
|
|
func() bool {
|
|
for i := 0; i <= len(str)-len(substr); i++ {
|
|
if str[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}()
|
|
}
|
|
|
|
// Benchmarks for security operations
|
|
|
|
func BenchmarkSecurityPolicyCheck(b *testing.B) {
|
|
roles := []string{"admin", "backend_developer", "frontend_developer", "security_expert"}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
role := roles[i%len(roles)]
|
|
checkStoreAccessPolicyTest(role)
|
|
}
|
|
}
|
|
|
|
func BenchmarkAuditLogging(b *testing.B) {
|
|
cfg := &config.Config{
|
|
Agent: config.AgentConfig{ID: "bench-agent", Role: "backend_developer"},
|
|
Security: config.SecurityConfig{AuditLogging: true, AuditPath: "/tmp/bench-audit.log"},
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
simulateAuditOperation(cfg, "store", "test:address:bench:task", "backend_developer", true, "")
|
|
}
|
|
}
|
|
|
|
func BenchmarkKeyRotationCheck(b *testing.B) {
|
|
cfg := &config.Config{
|
|
Agent: config.AgentConfig{ID: "bench-agent"},
|
|
Security: config.SecurityConfig{KeyRotationDays: 90, AuditLogging: true},
|
|
}
|
|
|
|
mockLogger := &MockAuditLogger{events: make([]*SecurityEvent, 0)}
|
|
mockKeyStore := &MockKeyStore{
|
|
keys: make(map[string]*SecureKeyData),
|
|
metadata: []*KeyMetadata{},
|
|
}
|
|
|
|
// Add some test keys
|
|
for i := 0; i < 10; i++ {
|
|
keyMeta := &KeyMetadata{
|
|
KeyID: fmt.Sprintf("bench-key-%d", i),
|
|
KeyType: "age-x25519",
|
|
RoleID: "backend_developer",
|
|
CreatedAt: time.Now().Add(-time.Duration(i*10) * 24 * time.Hour),
|
|
Status: KeyStatusActive,
|
|
}
|
|
mockKeyStore.metadata = append(mockKeyStore.metadata, keyMeta)
|
|
}
|
|
|
|
km, err := NewKeyManager(cfg, mockKeyStore, mockLogger)
|
|
if err != nil {
|
|
b.Fatalf("Failed to create key manager: %v", err)
|
|
}
|
|
defer func() {
|
|
if km.rotationScheduler.running {
|
|
km.rotationScheduler.Stop()
|
|
}
|
|
}()
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
km.checkKeysForRotation()
|
|
}
|
|
} |