Complete BZZZ functionality port to CHORUS
🎭 CHORUS now contains full BZZZ functionality adapted for containers Core systems ported: - P2P networking (libp2p with DHT and PubSub) - Task coordination (COOEE protocol) - HMMM collaborative reasoning - SHHH encryption and security - SLURP admin election system - UCXL content addressing - UCXI server integration - Hypercore logging system - Health monitoring and graceful shutdown - License validation with KACHING Container adaptations: - Environment variable configuration (no YAML files) - Container-optimized logging to stdout/stderr - Auto-generated agent IDs for container deployments - Docker-first architecture All proven BZZZ P2P protocols, AI integration, and collaboration features are now available in containerized form. Next: Build and test container deployment. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
857
pkg/crypto/README.md
Normal file
857
pkg/crypto/README.md
Normal file
@@ -0,0 +1,857 @@
|
||||
# BZZZ Role-Based Encryption System
|
||||
|
||||
## Overview
|
||||
|
||||
The BZZZ Role-Based Encryption System provides enterprise-grade security for the SLURP (Storage, Logic, Understanding, Retrieval, Processing) contextual intelligence system. This comprehensive encryption scheme implements multi-layer encryption, sophisticated access controls, and compliance monitoring to ensure that each AI agent role receives exactly the contextual understanding they need while maintaining strict security boundaries.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Architecture Overview](#architecture-overview)
|
||||
- [Security Features](#security-features)
|
||||
- [Role Access Matrix](#role-access-matrix)
|
||||
- [Implementation Components](#implementation-components)
|
||||
- [Usage Examples](#usage-examples)
|
||||
- [Security Considerations](#security-considerations)
|
||||
- [Compliance Features](#compliance-features)
|
||||
- [Performance Characteristics](#performance-characteristics)
|
||||
- [Testing](#testing)
|
||||
- [Deployment](#deployment)
|
||||
- [Monitoring and Alerts](#monitoring-and-alerts)
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The role-based encryption system is built on a multi-layer architecture that provides defense-in-depth security:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ SLURP Context Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Role-Based Encryption Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Access Control Matrix │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Key Management Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Age Encryption Foundation │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Audit & Logging │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **RoleCrypto** (`role_crypto.go`): Main encryption/decryption engine with multi-layer encryption
|
||||
2. **KeyManager** (`key_manager.go`): Sophisticated key management with rotation and recovery
|
||||
3. **AccessControlMatrix** (`access_control.go`): Dynamic access control with policy evaluation
|
||||
4. **AuditLogger** (`audit_logger.go`): Comprehensive audit logging and compliance monitoring
|
||||
|
||||
## Security Features
|
||||
|
||||
### Multi-Layer Encryption
|
||||
|
||||
The system implements sophisticated multi-layer encryption where different roles receive different encryption layers:
|
||||
|
||||
- **Base Context Encryption**: Core context data encrypted with Age X25519
|
||||
- **Role-Specific Overlays**: Additional encryption layers based on role hierarchy
|
||||
- **Compartmentalized Access**: Strict isolation between role access levels
|
||||
- **Forward Secrecy**: Regular key rotation ensures forward secrecy
|
||||
|
||||
### Access Control Matrix
|
||||
|
||||
The access control matrix implements multiple security models:
|
||||
|
||||
- **RBAC (Role-Based Access Control)**: Traditional role-based permissions
|
||||
- **ABAC (Attribute-Based Access Control)**: Context-aware attribute evaluation
|
||||
- **ReBAC (Relationship-Based Access Control)**: Hierarchical role relationships
|
||||
- **Zero-Trust Architecture**: Never trust, always verify principle
|
||||
|
||||
### Key Management
|
||||
|
||||
Enterprise-grade key management includes:
|
||||
|
||||
- **Hierarchical Key Derivation**: PBKDF2-based key derivation from role definitions
|
||||
- **Automated Key Rotation**: Configurable rotation policies with grace periods
|
||||
- **Emergency Key Recovery**: Shamir secret sharing for disaster recovery
|
||||
- **Key Escrow**: Secure key backup and restoration capabilities
|
||||
|
||||
## Role Access Matrix
|
||||
|
||||
The system defines a comprehensive role hierarchy with specific access levels:
|
||||
|
||||
| Role | Access Level | Scope | Capabilities |
|
||||
|------|-------------|-------|--------------|
|
||||
| **Senior Architect** | Critical | System-wide | Full architecture access, all contexts |
|
||||
| **Project Manager** | Critical | Global coordination | All contexts for project coordination |
|
||||
| **DevOps Engineer** | High | Infrastructure | Infrastructure + backend + security contexts |
|
||||
| **Security Engineer** | High | Security oversight | All contexts for security review |
|
||||
| **Backend Developer** | Medium | Backend scope | Backend + API + database contexts |
|
||||
| **Frontend Developer** | Medium | Frontend scope | Frontend + UI + component contexts |
|
||||
| **QA Engineer** | Medium | Testing scope | Testing + quality + dev contexts |
|
||||
| **Data Analyst** | Low | Analytics scope | Data + analytics + reporting contexts |
|
||||
| **Intern** | Low | Training scope | Training + documentation contexts |
|
||||
| **External Contractor** | Low | Limited scope | Limited access contexts only |
|
||||
|
||||
### Access Level Definitions
|
||||
|
||||
- **Critical (Level 4)**: Highly classified information for master roles only
|
||||
- **High (Level 3)**: Sensitive information for decision-making roles
|
||||
- **Medium (Level 2)**: Confidential information for coordination roles
|
||||
- **Low (Level 1)**: Basic encrypted information for standard roles
|
||||
- **Public (Level 0)**: Public information, no encryption required
|
||||
|
||||
## Implementation Components
|
||||
|
||||
### 1. Role-Based Encryption (`role_crypto.go`)
|
||||
|
||||
```go
|
||||
// Encrypt context for multiple roles with layered encryption
|
||||
encryptedData, err := roleCrypto.EncryptContextForRoles(
|
||||
contextNode,
|
||||
[]string{"backend_developer", "senior_architect"},
|
||||
[]string{"development", "security"}
|
||||
)
|
||||
|
||||
// Decrypt context with role-specific filtering
|
||||
decryptedContext, err := roleCrypto.DecryptContextForRole(
|
||||
encryptedData,
|
||||
"backend_developer"
|
||||
)
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Multi-recipient Age encryption
|
||||
- Role-specific context filtering
|
||||
- Inheritance-based access control
|
||||
- Automated audit logging
|
||||
|
||||
### 2. Key Management (`key_manager.go`)
|
||||
|
||||
```go
|
||||
// Generate role-specific encryption keys
|
||||
keyPair, err := keyManager.GenerateRoleKey("backend_developer", "age-x25519")
|
||||
|
||||
// Rotate keys with comprehensive logging
|
||||
result, err := keyManager.RotateKey("backend_developer", "scheduled_rotation")
|
||||
|
||||
// Emergency key recovery
|
||||
emergencyKey, err := emergencyManager.CreateEmergencyKey(
|
||||
"age-x25519",
|
||||
emergencyPolicy
|
||||
)
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Hierarchical key derivation
|
||||
- Automated rotation scheduling
|
||||
- Emergency recovery procedures
|
||||
- Integrity verification
|
||||
|
||||
### 3. Access Control (`access_control.go`)
|
||||
|
||||
```go
|
||||
// Evaluate access request with full context
|
||||
decision, err := accessControl.CheckAccess(ctx, &AccessRequest{
|
||||
UserID: "user123",
|
||||
Roles: []string{"backend_developer"},
|
||||
Resource: "context://sensitive/data",
|
||||
Action: "read",
|
||||
})
|
||||
|
||||
// Create temporary bypass for emergencies
|
||||
bypassToken, err := accessControl.CreateBypassToken(
|
||||
"admin_user",
|
||||
"Emergency maintenance",
|
||||
[]string{"context://emergency/*"},
|
||||
1*time.Hour,
|
||||
5
|
||||
)
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Dynamic policy evaluation
|
||||
- Context-aware decisions
|
||||
- Emergency bypass procedures
|
||||
- Comprehensive audit trails
|
||||
|
||||
### 4. Audit Logging (`audit_logger.go`)
|
||||
|
||||
```go
|
||||
// Comprehensive access logging
|
||||
auditLogger.LogAccess(&AccessLogEntry{
|
||||
UserID: "user123",
|
||||
Role: "backend_developer",
|
||||
AccessType: "decrypt",
|
||||
Success: true,
|
||||
AccessTime: time.Now(),
|
||||
})
|
||||
|
||||
// Security event monitoring
|
||||
auditLogger.LogSecurityEvent(&SecurityEvent{
|
||||
EventType: "suspicious_access",
|
||||
UserID: "user123",
|
||||
RiskLevel: "high",
|
||||
Details: eventDetails,
|
||||
})
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Real-time event correlation
|
||||
- Anomaly detection
|
||||
- Compliance reporting
|
||||
- Forensic investigation support
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Encryption/Decryption Workflow
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/anthonyrawlins/bzzz/pkg/config"
|
||||
"github.com/anthonyrawlins/bzzz/pkg/crypto"
|
||||
"github.com/anthonyrawlins/bzzz/pkg/ucxl"
|
||||
slurpContext "github.com/anthonyrawlins/bzzz/pkg/slurp/context"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize system components
|
||||
cfg := &config.Config{
|
||||
Agent: config.Agent{
|
||||
ID: "agent001",
|
||||
Role: "backend_developer",
|
||||
},
|
||||
}
|
||||
|
||||
auditLogger := crypto.NewAuditLogger(cfg, auditStorage)
|
||||
ageCrypto := crypto.NewAgeCrypto(cfg)
|
||||
adminKeyManager := crypto.NewAdminKeyManager(cfg, "node001")
|
||||
|
||||
roleCrypto, err := crypto.NewRoleCrypto(cfg, ageCrypto, adminKeyManager, auditLogger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create context to encrypt
|
||||
address, _ := ucxl.Parse("context://project/backend/api")
|
||||
contextNode := &slurpContext.ContextNode{
|
||||
Path: "/project/backend/api",
|
||||
UCXLAddress: address,
|
||||
Summary: "Backend API implementation context",
|
||||
Purpose: "Provides context for API development",
|
||||
Technologies: []string{"go", "rest", "database"},
|
||||
Tags: []string{"backend", "api"},
|
||||
Insights: []string{"Use proper error handling", "Implement rate limiting"},
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.95,
|
||||
EncryptedFor: []string{"backend_developer", "senior_architect"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
}
|
||||
|
||||
// Encrypt for multiple roles
|
||||
targetRoles := []string{"backend_developer", "senior_architect", "devops_engineer"}
|
||||
compartmentTags := []string{"development", "api"}
|
||||
|
||||
encryptedData, err := roleCrypto.EncryptContextForRoles(
|
||||
contextNode,
|
||||
targetRoles,
|
||||
compartmentTags
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Context encrypted with %d layers\n", len(encryptedData.EncryptedLayers))
|
||||
|
||||
// Decrypt with specific role
|
||||
decryptedContext, err := roleCrypto.DecryptContextForRole(
|
||||
encryptedData,
|
||||
"backend_developer"
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Decrypted context: %s\n", decryptedContext.Summary)
|
||||
fmt.Printf("Role-specific insights: %v\n", decryptedContext.Insights)
|
||||
}
|
||||
```
|
||||
|
||||
### Access Control Evaluation
|
||||
|
||||
```go
|
||||
func evaluateAccess() {
|
||||
// Create access request
|
||||
ctx := context.Background()
|
||||
request := &crypto.AccessRequest{
|
||||
RequestID: "req_001",
|
||||
Timestamp: time.Now(),
|
||||
UserID: "user123",
|
||||
Roles: []string{"backend_developer"},
|
||||
Resource: "context://sensitive/financial",
|
||||
ResourceType: "context",
|
||||
Action: "read",
|
||||
ActionType: "data_access",
|
||||
SessionID: "session_001",
|
||||
IPAddress: "192.168.1.100",
|
||||
UserAgent: "SLURP-Client/1.0",
|
||||
Justification: "Need financial context for feature development",
|
||||
}
|
||||
|
||||
// Evaluate access
|
||||
decision, err := accessControl.CheckAccess(ctx, request)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch decision.Decision {
|
||||
case crypto.DecisionPermit:
|
||||
fmt.Printf("Access granted: %s\n", decision.Reason)
|
||||
|
||||
// Check for obligations
|
||||
for _, obligation := range decision.Obligations {
|
||||
if obligation.Type == "approval" {
|
||||
fmt.Printf("Approval required: %s\n", obligation.Action)
|
||||
}
|
||||
}
|
||||
|
||||
case crypto.DecisionDeny:
|
||||
fmt.Printf("Access denied: %s\n", decision.Reason)
|
||||
fmt.Printf("Risk score: %.2f\n", decision.RiskScore)
|
||||
|
||||
default:
|
||||
fmt.Printf("Evaluation error: %s\n", decision.Reason)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key Rotation Management
|
||||
|
||||
```go
|
||||
func manageKeyRotation() {
|
||||
// Schedule automatic key rotation
|
||||
policy := &crypto.KeyRotationPolicy{
|
||||
RotationInterval: 30 * 24 * time.Hour, // 30 days
|
||||
MaxKeyAge: 90 * 24 * time.Hour, // 90 days
|
||||
AutoRotate: true,
|
||||
GracePeriod: 7 * 24 * time.Hour, // 7 days
|
||||
RequireQuorum: true,
|
||||
MinQuorumSize: 3,
|
||||
}
|
||||
|
||||
err := rotationScheduler.ScheduleKeyRotation("backend_developer", policy)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Manual key rotation
|
||||
result, err := keyManager.RotateKey("backend_developer", "security_incident")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Rotated keys for roles: %v\n", result.RotatedRoles)
|
||||
fmt.Printf("Rotation took: %v\n", result.RotationTime)
|
||||
|
||||
// Verify key integrity
|
||||
for role := range result.NewKeys {
|
||||
keyID := fmt.Sprintf("%s_age-x25519_v%d", role, result.NewKeys[role].Version)
|
||||
verification, err := keyManager.VerifyKeyIntegrity(keyID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if verification.OverallResult == "passed" {
|
||||
fmt.Printf("Key integrity verified for role: %s\n", role)
|
||||
} else {
|
||||
fmt.Printf("Key integrity issues for role %s: %v\n", role, verification.Issues)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Threat Model
|
||||
|
||||
The system is designed to protect against:
|
||||
|
||||
1. **External Threats**
|
||||
- Network eavesdropping and man-in-the-middle attacks
|
||||
- Unauthorized access attempts
|
||||
- Data exfiltration attempts
|
||||
- Malicious insider threats
|
||||
|
||||
2. **Internal Threats**
|
||||
- Privilege escalation attempts
|
||||
- Cross-role information leakage
|
||||
- Unauthorized key access
|
||||
- Policy bypass attempts
|
||||
|
||||
3. **System Threats**
|
||||
- Key compromise scenarios
|
||||
- System component failures
|
||||
- Configuration tampering
|
||||
- Audit log manipulation
|
||||
|
||||
### Security Measures
|
||||
|
||||
1. **Encryption Security**
|
||||
- Age X25519 elliptic curve cryptography
|
||||
- Multi-layer encryption with role-specific keys
|
||||
- Perfect forward secrecy through key rotation
|
||||
- Tamper-proof integrity verification
|
||||
|
||||
2. **Access Control Security**
|
||||
- Zero-trust architecture principles
|
||||
- Context-aware authorization decisions
|
||||
- Dynamic policy evaluation
|
||||
- Real-time threat intelligence integration
|
||||
|
||||
3. **Key Management Security**
|
||||
- Hierarchical key derivation using PBKDF2
|
||||
- Secure key storage with encryption at rest
|
||||
- Emergency recovery using Shamir secret sharing
|
||||
- Automated integrity monitoring
|
||||
|
||||
4. **Audit Security**
|
||||
- Immutable audit logs with cryptographic integrity
|
||||
- Real-time anomaly detection
|
||||
- Comprehensive forensic capabilities
|
||||
- Tamper-proof event correlation
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Deployment Security**
|
||||
- Use hardware security modules (HSMs) in production
|
||||
- Implement network segmentation
|
||||
- Enable comprehensive monitoring
|
||||
- Regular security assessments
|
||||
|
||||
2. **Operational Security**
|
||||
- Regular key rotation schedules
|
||||
- Principle of least privilege
|
||||
- Separation of duties
|
||||
- Incident response procedures
|
||||
|
||||
3. **Configuration Security**
|
||||
- Secure configuration management
|
||||
- Regular security policy reviews
|
||||
- Vulnerability management
|
||||
- Compliance monitoring
|
||||
|
||||
## Compliance Features
|
||||
|
||||
The system provides comprehensive compliance support for multiple standards:
|
||||
|
||||
### SOC 2 Type II Compliance
|
||||
|
||||
- **CC6.1 (Logical Access)**: Role-based access controls with comprehensive logging
|
||||
- **CC6.2 (System Access)**: Multi-factor authentication integration
|
||||
- **CC6.3 (Data Protection)**: Encryption at rest and in transit
|
||||
- **CC6.7 (System Access Removal)**: Automated key revocation procedures
|
||||
- **CC7.2 (System Monitoring)**: Real-time security monitoring and alerting
|
||||
|
||||
### ISO 27001 Compliance
|
||||
|
||||
- **A.9 (Access Control)**: Comprehensive access management framework
|
||||
- **A.10 (Cryptography)**: Enterprise-grade encryption implementation
|
||||
- **A.12 (Operations Security)**: Security operations and incident management
|
||||
- **A.16 (Information Security Incident Management)**: Automated incident response
|
||||
|
||||
### GDPR Compliance
|
||||
|
||||
- **Article 25 (Data Protection by Design)**: Privacy-by-design architecture
|
||||
- **Article 30 (Records of Processing)**: Comprehensive audit trails
|
||||
- **Article 32 (Security of Processing)**: State-of-the-art encryption
|
||||
- **Article 33 (Breach Notification)**: Automated breach detection and reporting
|
||||
|
||||
### NIST Cybersecurity Framework
|
||||
|
||||
- **Identify**: Asset and risk identification
|
||||
- **Protect**: Access controls and encryption
|
||||
- **Detect**: Continuous monitoring and anomaly detection
|
||||
- **Respond**: Automated incident response capabilities
|
||||
- **Recover**: Disaster recovery and business continuity
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
### Encryption Performance
|
||||
|
||||
| Operation | Typical Latency | Throughput |
|
||||
|-----------|----------------|------------|
|
||||
| Context Encryption | < 10ms | 1000+ ops/sec |
|
||||
| Context Decryption | < 5ms | 2000+ ops/sec |
|
||||
| Key Generation | < 100ms | 100+ ops/sec |
|
||||
| Access Evaluation | < 1ms | 10000+ ops/sec |
|
||||
|
||||
### Scalability Metrics
|
||||
|
||||
- **Concurrent Users**: 10,000+ simultaneous users
|
||||
- **Contexts**: 1M+ encrypted contexts
|
||||
- **Roles**: 1000+ distinct roles
|
||||
- **Policies**: 10,000+ access policies
|
||||
|
||||
### Optimization Features
|
||||
|
||||
1. **Caching**
|
||||
- Decision caching with configurable TTL
|
||||
- Policy compilation caching
|
||||
- Key fingerprint caching
|
||||
- User attribute caching
|
||||
|
||||
2. **Batching**
|
||||
- Batch encryption for multiple contexts
|
||||
- Batch audit log writes
|
||||
- Batch key operations
|
||||
- Batch policy evaluations
|
||||
|
||||
3. **Streaming**
|
||||
- Streaming encryption for large contexts
|
||||
- Streaming audit log processing
|
||||
- Streaming metric collection
|
||||
- Streaming compliance reporting
|
||||
|
||||
## Testing
|
||||
|
||||
The system includes comprehensive test coverage:
|
||||
|
||||
### Test Categories
|
||||
|
||||
1. **Unit Tests** (`role_crypto_test.go`)
|
||||
- Individual component functionality
|
||||
- Error handling and edge cases
|
||||
- Security vulnerability testing
|
||||
- Performance benchmarking
|
||||
|
||||
2. **Integration Tests**
|
||||
- End-to-end workflows
|
||||
- Component interaction testing
|
||||
- Configuration validation
|
||||
- Disaster recovery procedures
|
||||
|
||||
3. **Security Tests**
|
||||
- Penetration testing scenarios
|
||||
- Vulnerability assessments
|
||||
- Cryptographic validation
|
||||
- Access control verification
|
||||
|
||||
4. **Performance Tests**
|
||||
- Load testing under stress
|
||||
- Scalability validation
|
||||
- Memory usage optimization
|
||||
- Latency measurement
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
go test ./pkg/crypto/...
|
||||
|
||||
# Run with coverage
|
||||
go test -coverprofile=coverage.out ./pkg/crypto/...
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
# Run benchmarks
|
||||
go test -bench=. ./pkg/crypto/...
|
||||
|
||||
# Run security tests
|
||||
go test -tags=security ./pkg/crypto/...
|
||||
|
||||
# Run integration tests
|
||||
go test -tags=integration ./pkg/crypto/...
|
||||
```
|
||||
|
||||
### Test Results
|
||||
|
||||
Current test coverage: **95%+**
|
||||
|
||||
- Unit tests: 200+ test cases
|
||||
- Integration tests: 50+ scenarios
|
||||
- Security tests: 30+ vulnerability checks
|
||||
- Performance tests: 10+ benchmark suites
|
||||
|
||||
## Deployment
|
||||
|
||||
### Production Deployment
|
||||
|
||||
1. **Infrastructure Requirements**
|
||||
- Kubernetes cluster with RBAC enabled
|
||||
- Hardware Security Modules (HSMs)
|
||||
- Distributed storage for audit logs
|
||||
- Network segmentation and firewalls
|
||||
|
||||
2. **Configuration Management**
|
||||
- Secure configuration distribution
|
||||
- Environment-specific settings
|
||||
- Secret management integration
|
||||
- Policy version control
|
||||
|
||||
3. **Monitoring and Alerting**
|
||||
- Prometheus metrics collection
|
||||
- Grafana dashboards
|
||||
- Alert manager configuration
|
||||
- Log aggregation with ELK stack
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
bzzz-crypto:
|
||||
image: bzzz/crypto-service:latest
|
||||
environment:
|
||||
- BZZZ_CONFIG_PATH=/etc/bzzz/config.yaml
|
||||
- BZZZ_LOG_LEVEL=info
|
||||
- BZZZ_AUDIT_STORAGE=postgresql
|
||||
volumes:
|
||||
- ./config:/etc/bzzz
|
||||
- ./logs:/var/log/bzzz
|
||||
ports:
|
||||
- "8443:8443"
|
||||
depends_on:
|
||||
- postgresql
|
||||
- redis
|
||||
|
||||
postgresql:
|
||||
image: postgres:13
|
||||
environment:
|
||||
- POSTGRES_DB=bzzz_audit
|
||||
- POSTGRES_USER=bzzz
|
||||
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
secrets:
|
||||
- db_password
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
|
||||
secrets:
|
||||
db_password:
|
||||
file: ./secrets/db_password.txt
|
||||
```
|
||||
|
||||
### Kubernetes Deployment
|
||||
|
||||
```yaml
|
||||
# k8s-deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bzzz-crypto-service
|
||||
labels:
|
||||
app: bzzz-crypto
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: bzzz-crypto
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: bzzz-crypto
|
||||
spec:
|
||||
serviceAccountName: bzzz-crypto
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
containers:
|
||||
- name: crypto-service
|
||||
image: bzzz/crypto-service:v1.0.0
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8443
|
||||
name: https
|
||||
env:
|
||||
- name: BZZZ_CONFIG_PATH
|
||||
value: "/etc/bzzz/config.yaml"
|
||||
- name: BZZZ_LOG_LEVEL
|
||||
value: "info"
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/bzzz
|
||||
readOnly: true
|
||||
- name: secrets
|
||||
mountPath: /etc/secrets
|
||||
readOnly: true
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8443
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 8443
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: bzzz-crypto-config
|
||||
- name: secrets
|
||||
secret:
|
||||
secretName: bzzz-crypto-secrets
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: bzzz-crypto-service
|
||||
spec:
|
||||
selector:
|
||||
app: bzzz-crypto
|
||||
ports:
|
||||
- port: 443
|
||||
targetPort: 8443
|
||||
name: https
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
## Monitoring and Alerts
|
||||
|
||||
### Metrics Collection
|
||||
|
||||
The system exposes comprehensive metrics for monitoring:
|
||||
|
||||
```go
|
||||
// Security metrics
|
||||
security_events_total{type="access_denied",role="backend_developer"}
|
||||
security_risk_score{user="user123",resource="context://sensitive/*"}
|
||||
encryption_operations_total{operation="encrypt",role="backend_developer"}
|
||||
decryption_operations_total{operation="decrypt",role="backend_developer"}
|
||||
|
||||
// Performance metrics
|
||||
encryption_duration_seconds{operation="encrypt",role="backend_developer"}
|
||||
decryption_duration_seconds{operation="decrypt",role="backend_developer"}
|
||||
access_evaluation_duration_seconds{decision="permit",role="backend_developer"}
|
||||
key_rotation_duration_seconds{role="backend_developer"}
|
||||
|
||||
// System health metrics
|
||||
active_sessions_total{role="backend_developer"}
|
||||
cache_hit_ratio{cache_type="decision"}
|
||||
audit_events_total{type="access_log"}
|
||||
key_integrity_status{role="backend_developer",status="valid"}
|
||||
```
|
||||
|
||||
### Alerting Rules
|
||||
|
||||
```yaml
|
||||
# Prometheus alerting rules
|
||||
groups:
|
||||
- name: bzzz_crypto_security
|
||||
rules:
|
||||
- alert: HighSecurityRiskAccess
|
||||
expr: security_risk_score > 0.8
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "High risk access detected"
|
||||
description: "User {{ $labels.user }} attempted high-risk access to {{ $labels.resource }}"
|
||||
|
||||
- alert: UnauthorizedAccessAttempt
|
||||
expr: increase(security_events_total{type="access_denied"}[5m]) > 10
|
||||
for: 1m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Multiple unauthorized access attempts"
|
||||
description: "{{ $value }} unauthorized access attempts in 5 minutes"
|
||||
|
||||
- alert: KeyIntegrityFailure
|
||||
expr: key_integrity_status{status="invalid"} > 0
|
||||
for: 0s
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Key integrity failure detected"
|
||||
description: "Key integrity check failed for role {{ $labels.role }}"
|
||||
|
||||
- alert: AuditLogFailure
|
||||
expr: increase(audit_log_errors_total[5m]) > 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Audit log failure"
|
||||
description: "Audit logging is failing - compliance risk"
|
||||
```
|
||||
|
||||
### Dashboard Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"dashboard": {
|
||||
"title": "BZZZ Crypto Security Dashboard",
|
||||
"panels": [
|
||||
{
|
||||
"title": "Security Events",
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(security_events_total[5m]))",
|
||||
"legendFormat": "Events/sec"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Access Decisions",
|
||||
"type": "pie",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (decision) (access_decisions_total)",
|
||||
"legendFormat": "{{ decision }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Encryption Performance",
|
||||
"type": "graph",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, rate(encryption_duration_seconds_bucket[5m]))",
|
||||
"legendFormat": "95th percentile"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The BZZZ Role-Based Encryption System provides enterprise-grade security for contextual intelligence with comprehensive features including multi-layer encryption, sophisticated access controls, automated key management, and extensive compliance monitoring. The system is designed to scale to enterprise requirements while maintaining the highest security standards and providing complete audit transparency.
|
||||
|
||||
For additional information, support, or contributions, please refer to the project documentation or contact the security team.
|
||||
|
||||
---
|
||||
|
||||
**Security Notice**: This system handles sensitive contextual information. Always follow security best practices, keep systems updated, and conduct regular security assessments. Report any security issues immediately to the security team.
|
||||
|
||||
**Compliance Notice**: This system is designed to meet multiple compliance standards. Ensure proper configuration and monitoring for your specific compliance requirements. Regular compliance audits are recommended.
|
||||
|
||||
**Performance Notice**: While the system is optimized for performance, encryption and access control operations have computational overhead. Plan capacity accordingly and monitor performance metrics in production environments.
|
||||
492
pkg/crypto/age_crypto.go
Normal file
492
pkg/crypto/age_crypto.go
Normal file
@@ -0,0 +1,492 @@
|
||||
// Package crypto provides Age encryption implementation for role-based content security in BZZZ.
|
||||
//
|
||||
// This package implements the cryptographic foundation for BZZZ Phase 2B, enabling:
|
||||
// - Role-based content encryption using Age (https://age-encryption.org)
|
||||
// - Hierarchical access control based on agent authority levels
|
||||
// - Multi-recipient encryption for shared content
|
||||
// - Secure key management and validation
|
||||
//
|
||||
// The Age encryption system ensures that UCXL content is encrypted before storage
|
||||
// in the distributed DHT, with access control enforced through role-based key distribution.
|
||||
//
|
||||
// Architecture Overview:
|
||||
// - Each role has an Age key pair (public/private)
|
||||
// - Content is encrypted for specific roles based on creator's authority
|
||||
// - Higher authority roles can decrypt lower authority content
|
||||
// - Admin roles can decrypt all content in the system
|
||||
//
|
||||
// Security Model:
|
||||
// - X25519 elliptic curve cryptography (Age standard)
|
||||
// - Per-role key pairs for access segmentation
|
||||
// - Authority hierarchy prevents privilege escalation
|
||||
// - Shamir secret sharing for admin key distribution (see shamir.go)
|
||||
//
|
||||
// Cross-references:
|
||||
// - pkg/config/roles.go: Role definitions and authority levels
|
||||
// - pkg/dht/encrypted_storage.go: Encrypted DHT storage implementation
|
||||
// - pkg/ucxl/decision_publisher.go: Decision publishing with encryption
|
||||
// - docs/ARCHITECTURE.md: Complete system architecture
|
||||
// - docs/SECURITY.md: Security model and threat analysis
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"filippo.io/age" // Modern, secure encryption library
|
||||
"chorus.services/bzzz/pkg/config"
|
||||
)
|
||||
|
||||
// AgeCrypto handles Age encryption for role-based content security.
|
||||
//
|
||||
// This is the primary interface for encrypting and decrypting UCXL content
|
||||
// based on BZZZ role hierarchies. It provides methods to:
|
||||
// - Encrypt content for specific roles or multiple roles
|
||||
// - Decrypt content using the current agent's role key
|
||||
// - Validate Age key formats and generate new key pairs
|
||||
// - Determine decryption permissions based on role authority
|
||||
//
|
||||
// Usage Example:
|
||||
// crypto := NewAgeCrypto(config)
|
||||
// encrypted, err := crypto.EncryptForRole(content, "backend_developer")
|
||||
// decrypted, err := crypto.DecryptWithRole(encrypted)
|
||||
//
|
||||
// Thread Safety: AgeCrypto is safe for concurrent use across goroutines.
|
||||
type AgeCrypto struct {
|
||||
config *config.Config // BZZZ configuration containing role definitions
|
||||
}
|
||||
|
||||
// NewAgeCrypto creates a new Age crypto handler for role-based encryption.
|
||||
//
|
||||
// Parameters:
|
||||
// cfg: BZZZ configuration containing role definitions and agent settings
|
||||
//
|
||||
// Returns:
|
||||
// *AgeCrypto: Configured crypto handler ready for encryption/decryption
|
||||
//
|
||||
// The returned AgeCrypto instance will use the role definitions from the
|
||||
// provided configuration to determine encryption permissions and key access.
|
||||
//
|
||||
// Cross-references:
|
||||
// - pkg/config/config.go: Configuration structure
|
||||
// - pkg/config/roles.go: Role definitions and authority levels
|
||||
func NewAgeCrypto(cfg *config.Config) *AgeCrypto {
|
||||
return &AgeCrypto{
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateAgeKeyPair generates a new Age X25519 key pair for role-based encryption.
|
||||
//
|
||||
// This function creates cryptographically secure Age key pairs suitable for
|
||||
// role-based content encryption. Each role in BZZZ should have its own key pair
|
||||
// to enable proper access control and content segmentation.
|
||||
//
|
||||
// Returns:
|
||||
// *config.AgeKeyPair: Structure containing both public and private keys
|
||||
// error: Any error during key generation
|
||||
//
|
||||
// Key Format:
|
||||
// - Private key: "AGE-SECRET-KEY-1..." (Age standard format)
|
||||
// - Public key: "age1..." (Age recipient format)
|
||||
//
|
||||
// Security Notes:
|
||||
// - Uses X25519 elliptic curve cryptography
|
||||
// - Keys are cryptographically random using crypto/rand
|
||||
// - Private keys should be stored securely and never shared
|
||||
// - Public keys can be distributed freely for encryption
|
||||
//
|
||||
// Usage:
|
||||
// keyPair, err := GenerateAgeKeyPair()
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("key generation failed: %w", err)
|
||||
// }
|
||||
// // Store keyPair.PrivateKey securely
|
||||
// // Distribute keyPair.PublicKey for encryption
|
||||
//
|
||||
// Cross-references:
|
||||
// - pkg/config/roles.go: AgeKeyPair structure definition
|
||||
// - docs/SECURITY.md: Key management best practices
|
||||
// - pkg/crypto/shamir.go: Admin key distribution via secret sharing
|
||||
func GenerateAgeKeyPair() (*config.AgeKeyPair, error) {
|
||||
// Generate X25519 identity using Age's secure random generation
|
||||
identity, err := age.GenerateX25519Identity()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate Age identity: %w", err)
|
||||
}
|
||||
|
||||
// Extract public and private key strings in Age format
|
||||
return &config.AgeKeyPair{
|
||||
PublicKey: identity.Recipient().String(), // "age1..." format for recipients
|
||||
PrivateKey: identity.String(), // "AGE-SECRET-KEY-1..." format
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseAgeIdentity parses an Age private key string into a usable identity.
|
||||
//
|
||||
// This function converts a private key string (AGE-SECRET-KEY-1...) into
|
||||
// an Age identity that can be used for decryption operations.
|
||||
//
|
||||
// Parameters:
|
||||
// privateKey: Age private key string in standard format
|
||||
//
|
||||
// Returns:
|
||||
// age.Identity: Parsed identity for decryption operations
|
||||
// error: Parsing error if key format is invalid
|
||||
//
|
||||
// Key Format Requirements:
|
||||
// - Must start with "AGE-SECRET-KEY-1"
|
||||
// - Must be properly formatted X25519 private key
|
||||
// - Must be base64-encoded as per Age specification
|
||||
//
|
||||
// Cross-references:
|
||||
// - DecryptWithPrivateKey(): Uses parsed identities for decryption
|
||||
// - ValidateAgeKey(): Validates key format before parsing
|
||||
func ParseAgeIdentity(privateKey string) (age.Identity, error) {
|
||||
return age.ParseX25519Identity(privateKey)
|
||||
}
|
||||
|
||||
// ParseAgeRecipient parses an Age public key string into a recipient.
|
||||
//
|
||||
// This function converts a public key string (age1...) into an Age recipient
|
||||
// that can be used for encryption operations.
|
||||
//
|
||||
// Parameters:
|
||||
// publicKey: Age public key string in recipient format
|
||||
//
|
||||
// Returns:
|
||||
// age.Recipient: Parsed recipient for encryption operations
|
||||
// error: Parsing error if key format is invalid
|
||||
//
|
||||
// Key Format Requirements:
|
||||
// - Must start with "age1"
|
||||
// - Must be properly formatted X25519 public key
|
||||
// - Must be base32-encoded as per Age specification
|
||||
//
|
||||
// Cross-references:
|
||||
// - EncryptForRole(): Uses parsed recipients for encryption
|
||||
// - ValidateAgeKey(): Validates key format before parsing
|
||||
func ParseAgeRecipient(publicKey string) (age.Recipient, error) {
|
||||
return age.ParseX25519Recipient(publicKey)
|
||||
}
|
||||
|
||||
// EncryptForRole encrypts content for a specific role using Age encryption
|
||||
func (ac *AgeCrypto) EncryptForRole(content []byte, roleName string) ([]byte, error) {
|
||||
// Get role definition
|
||||
roles := config.GetPredefinedRoles()
|
||||
role, exists := roles[roleName]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("role '%s' not found", roleName)
|
||||
}
|
||||
|
||||
// Check if role has Age keys configured
|
||||
if role.AgeKeys.PublicKey == "" {
|
||||
return nil, fmt.Errorf("role '%s' has no Age public key configured", roleName)
|
||||
}
|
||||
|
||||
// Parse the recipient
|
||||
recipient, err := ParseAgeRecipient(role.AgeKeys.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Age recipient for role '%s': %w", roleName, err)
|
||||
}
|
||||
|
||||
// Encrypt the content
|
||||
out := &bytes.Buffer{}
|
||||
w, err := age.Encrypt(out, recipient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Age encryptor: %w", err)
|
||||
}
|
||||
|
||||
if _, err := w.Write(content); err != nil {
|
||||
return nil, fmt.Errorf("failed to write content to Age encryptor: %w", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, fmt.Errorf("failed to close Age encryptor: %w", err)
|
||||
}
|
||||
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
// EncryptForMultipleRoles encrypts content for multiple roles
|
||||
func (ac *AgeCrypto) EncryptForMultipleRoles(content []byte, roleNames []string) ([]byte, error) {
|
||||
if len(roleNames) == 0 {
|
||||
return nil, fmt.Errorf("no roles specified")
|
||||
}
|
||||
|
||||
var recipients []age.Recipient
|
||||
roles := config.GetPredefinedRoles()
|
||||
|
||||
// Collect all recipients
|
||||
for _, roleName := range roleNames {
|
||||
role, exists := roles[roleName]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("role '%s' not found", roleName)
|
||||
}
|
||||
|
||||
if role.AgeKeys.PublicKey == "" {
|
||||
return nil, fmt.Errorf("role '%s' has no Age public key configured", roleName)
|
||||
}
|
||||
|
||||
recipient, err := ParseAgeRecipient(role.AgeKeys.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Age recipient for role '%s': %w", roleName, err)
|
||||
}
|
||||
|
||||
recipients = append(recipients, recipient)
|
||||
}
|
||||
|
||||
// Encrypt for all recipients
|
||||
out := &bytes.Buffer{}
|
||||
w, err := age.Encrypt(out, recipients...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Age encryptor: %w", err)
|
||||
}
|
||||
|
||||
if _, err := w.Write(content); err != nil {
|
||||
return nil, fmt.Errorf("failed to write content to Age encryptor: %w", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, fmt.Errorf("failed to close Age encryptor: %w", err)
|
||||
}
|
||||
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
// DecryptWithRole decrypts content using the current agent's role key
|
||||
func (ac *AgeCrypto) DecryptWithRole(encryptedContent []byte) ([]byte, error) {
|
||||
if ac.config.Agent.Role == "" {
|
||||
return nil, fmt.Errorf("no role configured for current agent")
|
||||
}
|
||||
|
||||
// Get current role's private key
|
||||
roles := config.GetPredefinedRoles()
|
||||
role, exists := roles[ac.config.Agent.Role]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("current role '%s' not found", ac.config.Agent.Role)
|
||||
}
|
||||
|
||||
if role.AgeKeys.PrivateKey == "" {
|
||||
return nil, fmt.Errorf("current role '%s' has no Age private key configured", ac.config.Agent.Role)
|
||||
}
|
||||
|
||||
return ac.DecryptWithPrivateKey(encryptedContent, role.AgeKeys.PrivateKey)
|
||||
}
|
||||
|
||||
// DecryptWithPrivateKey decrypts content using a specific private key
|
||||
func (ac *AgeCrypto) DecryptWithPrivateKey(encryptedContent []byte, privateKey string) ([]byte, error) {
|
||||
// Parse the identity
|
||||
identity, err := ParseAgeIdentity(privateKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Age identity: %w", err)
|
||||
}
|
||||
|
||||
// Decrypt the content
|
||||
in := bytes.NewReader(encryptedContent)
|
||||
r, err := age.Decrypt(in, identity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt content: %w", err)
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
if _, err := io.Copy(out, r); err != nil {
|
||||
return nil, fmt.Errorf("failed to read decrypted content: %w", err)
|
||||
}
|
||||
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
// CanDecryptContent checks if current role can decrypt content encrypted for a target role
|
||||
func (ac *AgeCrypto) CanDecryptContent(targetRole string) (bool, error) {
|
||||
return ac.config.CanDecryptRole(targetRole)
|
||||
}
|
||||
|
||||
// GetDecryptableRoles returns list of roles current agent can decrypt
|
||||
func (ac *AgeCrypto) GetDecryptableRoles() ([]string, error) {
|
||||
if ac.config.Agent.Role == "" {
|
||||
return nil, fmt.Errorf("no role configured")
|
||||
}
|
||||
|
||||
roles := config.GetPredefinedRoles()
|
||||
currentRole, exists := roles[ac.config.Agent.Role]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("current role '%s' not found", ac.config.Agent.Role)
|
||||
}
|
||||
|
||||
return currentRole.CanDecrypt, nil
|
||||
}
|
||||
|
||||
// EncryptUCXLContent encrypts UCXL content based on creator's authority level
|
||||
func (ac *AgeCrypto) EncryptUCXLContent(content []byte, creatorRole string) ([]byte, error) {
|
||||
// Get roles that should be able to decrypt this content
|
||||
decryptableRoles, err := ac.getDecryptableRolesForCreator(creatorRole)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine decryptable roles: %w", err)
|
||||
}
|
||||
|
||||
// Encrypt for all decryptable roles
|
||||
return ac.EncryptForMultipleRoles(content, decryptableRoles)
|
||||
}
|
||||
|
||||
// getDecryptableRolesForCreator determines which roles should be able to decrypt content from a creator
|
||||
func (ac *AgeCrypto) getDecryptableRolesForCreator(creatorRole string) ([]string, error) {
|
||||
roles := config.GetPredefinedRoles()
|
||||
_, exists := roles[creatorRole]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("creator role '%s' not found", creatorRole)
|
||||
}
|
||||
|
||||
// Start with the creator role itself
|
||||
decryptableRoles := []string{creatorRole}
|
||||
|
||||
// Add all roles that have higher or equal authority and can decrypt this role
|
||||
for roleName, role := range roles {
|
||||
// Skip the creator role (already added)
|
||||
if roleName == creatorRole {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this role can decrypt the creator's content
|
||||
for _, decryptableRole := range role.CanDecrypt {
|
||||
if decryptableRole == creatorRole || decryptableRole == "*" {
|
||||
// Add this role to the list if not already present
|
||||
if !contains(decryptableRoles, roleName) {
|
||||
decryptableRoles = append(decryptableRoles, roleName)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decryptableRoles, nil
|
||||
}
|
||||
|
||||
// ValidateAgeKey validates an Age key format
|
||||
func ValidateAgeKey(key string, isPrivate bool) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("key cannot be empty")
|
||||
}
|
||||
|
||||
if isPrivate {
|
||||
// Validate private key format
|
||||
if !strings.HasPrefix(key, "AGE-SECRET-KEY-") {
|
||||
return fmt.Errorf("invalid Age private key format")
|
||||
}
|
||||
|
||||
// Try to parse it
|
||||
_, err := ParseAgeIdentity(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse Age private key: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Validate public key format
|
||||
if !strings.HasPrefix(key, "age1") {
|
||||
return fmt.Errorf("invalid Age public key format")
|
||||
}
|
||||
|
||||
// Try to parse it
|
||||
_, err := ParseAgeRecipient(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse Age public key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateRoleKeys generates Age key pairs for all roles that don't have them
|
||||
func GenerateRoleKeys() (map[string]*config.AgeKeyPair, error) {
|
||||
roleKeys := make(map[string]*config.AgeKeyPair)
|
||||
roles := config.GetPredefinedRoles()
|
||||
|
||||
for roleName, role := range roles {
|
||||
// Skip if role already has keys
|
||||
if role.AgeKeys.PublicKey != "" && role.AgeKeys.PrivateKey != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate new key pair
|
||||
keyPair, err := GenerateAgeKeyPair()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate keys for role '%s': %w", roleName, err)
|
||||
}
|
||||
|
||||
roleKeys[roleName] = keyPair
|
||||
}
|
||||
|
||||
return roleKeys, nil
|
||||
}
|
||||
|
||||
// TestAgeEncryption tests Age encryption/decryption with sample data
|
||||
func TestAgeEncryption() error {
|
||||
// Generate test key pair
|
||||
keyPair, err := GenerateAgeKeyPair()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate test key pair: %w", err)
|
||||
}
|
||||
|
||||
// Test content
|
||||
testContent := []byte("This is a test UCXL decision node content for Age encryption")
|
||||
|
||||
// Parse recipient and identity
|
||||
recipient, err := ParseAgeRecipient(keyPair.PublicKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse test recipient: %w", err)
|
||||
}
|
||||
|
||||
identity, err := ParseAgeIdentity(keyPair.PrivateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse test identity: %w", err)
|
||||
}
|
||||
|
||||
// Encrypt
|
||||
out := &bytes.Buffer{}
|
||||
w, err := age.Encrypt(out, recipient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create test encryptor: %w", err)
|
||||
}
|
||||
|
||||
if _, err := w.Write(testContent); err != nil {
|
||||
return fmt.Errorf("failed to write test content: %w", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close test encryptor: %w", err)
|
||||
}
|
||||
|
||||
encryptedContent := out.Bytes()
|
||||
|
||||
// Decrypt
|
||||
in := bytes.NewReader(encryptedContent)
|
||||
r, err := age.Decrypt(in, identity)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt test content: %w", err)
|
||||
}
|
||||
|
||||
decryptedBuffer := &bytes.Buffer{}
|
||||
if _, err := io.Copy(decryptedBuffer, r); err != nil {
|
||||
return fmt.Errorf("failed to read decrypted test content: %w", err)
|
||||
}
|
||||
|
||||
decryptedContent := decryptedBuffer.Bytes()
|
||||
|
||||
// Verify
|
||||
if !bytes.Equal(testContent, decryptedContent) {
|
||||
return fmt.Errorf("test failed: decrypted content doesn't match original")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// contains checks if a string slice contains a value
|
||||
func contains(slice []string, value string) bool {
|
||||
for _, item := range slice {
|
||||
if item == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
1044
pkg/crypto/audit_logger.go
Normal file
1044
pkg/crypto/audit_logger.go
Normal file
File diff suppressed because it is too large
Load Diff
1295
pkg/crypto/key_manager.go
Normal file
1295
pkg/crypto/key_manager.go
Normal file
File diff suppressed because it is too large
Load Diff
959
pkg/crypto/role_crypto_test.go
Normal file
959
pkg/crypto/role_crypto_test.go
Normal file
@@ -0,0 +1,959 @@
|
||||
// Package crypto_test provides comprehensive tests for role-based encryption.
|
||||
//
|
||||
// This test suite validates the enterprise-grade security features including:
|
||||
// - Multi-layer encryption and decryption operations
|
||||
// - Role-based access control and permission enforcement
|
||||
// - Key management and rotation procedures
|
||||
// - Audit logging and compliance monitoring
|
||||
// - Performance and security benchmarks
|
||||
// - Edge cases and error handling
|
||||
//
|
||||
// Test Categories:
|
||||
// - Unit tests for individual components
|
||||
// - Integration tests for end-to-end workflows
|
||||
// - Security tests for vulnerability assessment
|
||||
// - Performance tests for scalability validation
|
||||
// - Compliance tests for regulatory requirements
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"chorus.services/bzzz/pkg/config"
|
||||
"chorus.services/bzzz/pkg/ucxl"
|
||||
slurpContext "chorus.services/bzzz/pkg/slurp/context"
|
||||
)
|
||||
|
||||
// RoleCryptoTestSuite provides comprehensive testing for role-based encryption
|
||||
type RoleCryptoTestSuite struct {
|
||||
suite.Suite
|
||||
config *config.Config
|
||||
ageCrypto *AgeCrypto
|
||||
auditLogger *MockAuditLogger
|
||||
roleCrypto *RoleCrypto
|
||||
keyManager *KeyManager
|
||||
keyStore *MockKeyStore
|
||||
accessControl *AccessControlMatrix
|
||||
}
|
||||
|
||||
// MockAuditLogger implements AuditLogger for testing
|
||||
type MockAuditLogger struct {
|
||||
accessLogs []*AccessLogEntry
|
||||
keyRotations []*KeyRotationEvent
|
||||
securityEvents []*SecurityEvent
|
||||
}
|
||||
|
||||
func (m *MockAuditLogger) LogAccess(entry *AccessLogEntry) error {
|
||||
m.accessLogs = append(m.accessLogs, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockAuditLogger) LogKeyRotation(event *KeyRotationEvent) error {
|
||||
m.keyRotations = append(m.keyRotations, event)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockAuditLogger) LogSecurityEvent(event *SecurityEvent) error {
|
||||
m.securityEvents = append(m.securityEvents, event)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockAuditLogger) GetAuditTrail(criteria *AuditCriteria) ([]*AuditEvent, error) {
|
||||
events := []*AuditEvent{}
|
||||
for _, access := range m.accessLogs {
|
||||
events = append(events, &AuditEvent{
|
||||
EventID: fmt.Sprintf("access_%s", access.UserID),
|
||||
EventType: "access",
|
||||
Timestamp: access.AccessTime,
|
||||
UserID: access.UserID,
|
||||
Data: map[string]interface{}{
|
||||
"access_type": access.AccessType,
|
||||
"success": access.Success,
|
||||
},
|
||||
})
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// MockKeyStore implements KeyStore for testing
|
||||
type MockKeyStore struct {
|
||||
keys map[string]*SecureKeyData
|
||||
}
|
||||
|
||||
func NewMockKeyStore() *MockKeyStore {
|
||||
return &MockKeyStore{
|
||||
keys: make(map[string]*SecureKeyData),
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
metadata := []*KeyMetadata{}
|
||||
for keyID, keyData := range m.keys {
|
||||
if filter != nil && filter.RoleID != "" {
|
||||
if roleID, ok := keyData.Metadata["role_id"].(string); !ok || roleID != filter.RoleID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
meta := &KeyMetadata{
|
||||
KeyID: keyID,
|
||||
KeyType: keyData.KeyType,
|
||||
CreatedAt: keyData.CreatedAt,
|
||||
Status: keyData.Status,
|
||||
SecurityLevel: AccessMedium, // Default for tests
|
||||
}
|
||||
|
||||
if roleID, ok := keyData.Metadata["role_id"].(string); ok {
|
||||
meta.RoleID = roleID
|
||||
}
|
||||
|
||||
metadata = append(metadata, meta)
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (m *MockKeyStore) BackupKeys(criteria *BackupCriteria) (*KeyBackup, error) {
|
||||
return &KeyBackup{
|
||||
BackupID: fmt.Sprintf("backup_%d", time.Now().Unix()),
|
||||
CreatedAt: time.Now(),
|
||||
KeyCount: len(m.keys),
|
||||
Checksum: "mock_checksum",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockKeyStore) RestoreKeys(backup *KeyBackup) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MockPolicyEngine implements PolicyEngine for testing
|
||||
type MockPolicyEngine struct {
|
||||
policies map[string]*AccessPolicy
|
||||
}
|
||||
|
||||
func NewMockPolicyEngine() *MockPolicyEngine {
|
||||
return &MockPolicyEngine{
|
||||
policies: make(map[string]*AccessPolicy),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockPolicyEngine) EvaluatePolicy(ctx context.Context, request *AccessRequest) (*PolicyDecision, error) {
|
||||
// Simple mock evaluation - permit by default for testing
|
||||
decision := &PolicyDecision{
|
||||
RequestID: request.RequestID,
|
||||
Decision: DecisionPermit,
|
||||
Reason: "Mock policy evaluation - permit",
|
||||
MatchedPolicies: []string{"mock_policy"},
|
||||
AppliedRules: []string{"mock_rule"},
|
||||
ConfidenceScore: 0.95,
|
||||
RiskScore: 0.2,
|
||||
EvaluationTime: 10 * time.Millisecond,
|
||||
EvaluatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Deny access for test cases that need denial
|
||||
if strings.Contains(request.UserID, "unauthorized") {
|
||||
decision.Decision = DecisionDeny
|
||||
decision.Reason = "Unauthorized user"
|
||||
decision.RiskScore = 0.9
|
||||
}
|
||||
|
||||
return decision, nil
|
||||
}
|
||||
|
||||
func (m *MockPolicyEngine) CompilePolicy(policy *AccessPolicy) (*CompiledPolicy, error) {
|
||||
return &CompiledPolicy{
|
||||
PolicyID: policy.PolicyID,
|
||||
CompiledAt: time.Now(),
|
||||
CompilerVersion: "mock_v1.0",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockPolicyEngine) ValidatePolicy(policy *AccessPolicy) (*PolicyValidationResult, error) {
|
||||
return &PolicyValidationResult{
|
||||
Valid: true,
|
||||
Errors: []string{},
|
||||
Warnings: []string{},
|
||||
ValidatedAt: time.Now(),
|
||||
ValidationTime: 5 * time.Millisecond,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockPolicyEngine) LoadPolicies(policies []*AccessPolicy) error {
|
||||
for _, policy := range policies {
|
||||
m.policies[policy.PolicyID] = policy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPolicyEngine) ReloadPolicies() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MockAttributeProvider implements AttributeProvider for testing
|
||||
type MockAttributeProvider struct{}
|
||||
|
||||
func (m *MockAttributeProvider) GetUserAttributes(userID string) (*UserAttributes, error) {
|
||||
return &UserAttributes{
|
||||
UserID: userID,
|
||||
Department: "Engineering",
|
||||
Title: "Software Engineer",
|
||||
ClearanceLevel: "medium",
|
||||
EmploymentType: "full_time",
|
||||
StartDate: time.Now().AddDate(-1, 0, 0),
|
||||
Location: "headquarters",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockAttributeProvider) GetResourceAttributes(resource string) (*ResourceAttributes, error) {
|
||||
return &ResourceAttributes{
|
||||
ResourceID: resource,
|
||||
Classification: "internal",
|
||||
Sensitivity: "medium",
|
||||
DataType: "context",
|
||||
CreatedAt: time.Now().AddDate(0, -1, 0),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockAttributeProvider) GetEnvironmentAttributes() (*EnvironmentAttributes, error) {
|
||||
return &EnvironmentAttributes{
|
||||
CurrentTime: time.Now(),
|
||||
BusinessHours: true,
|
||||
NetworkZone: "internal",
|
||||
DeviceType: "workstation",
|
||||
DeviceTrust: "trusted",
|
||||
ConnectionType: "wired",
|
||||
ThreatLevel: "low",
|
||||
ComplianceMode: "standard",
|
||||
MaintenanceMode: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockAttributeProvider) GetContextAttributes(ctx context.Context) (*ContextAttributes, error) {
|
||||
return &ContextAttributes{
|
||||
RequestType: "api",
|
||||
ApplicationContext: "slurp_system",
|
||||
BusinessContext: "development",
|
||||
TechnicalContext: "microservice",
|
||||
ComplianceContext: "internal",
|
||||
RiskContext: "low",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetupSuite initializes the test suite
|
||||
func (suite *RoleCryptoTestSuite) SetupSuite() {
|
||||
// Create test configuration
|
||||
suite.config = &config.Config{
|
||||
Agent: config.Agent{
|
||||
ID: "test_agent",
|
||||
Role: "backend_developer",
|
||||
},
|
||||
}
|
||||
|
||||
// Initialize components
|
||||
suite.auditLogger = &MockAuditLogger{
|
||||
accessLogs: []*AccessLogEntry{},
|
||||
keyRotations: []*KeyRotationEvent{},
|
||||
securityEvents: []*SecurityEvent{},
|
||||
}
|
||||
|
||||
suite.ageCrypto = NewAgeCrypto(suite.config)
|
||||
|
||||
suite.keyStore = NewMockKeyStore()
|
||||
|
||||
var err error
|
||||
suite.keyManager, err = NewKeyManager(suite.config, suite.keyStore, suite.auditLogger)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
adminKeyManager := NewAdminKeyManager(suite.config, "test_node")
|
||||
|
||||
suite.roleCrypto, err = NewRoleCrypto(suite.config, suite.ageCrypto, adminKeyManager, suite.auditLogger)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Initialize access control
|
||||
policyEngine := NewMockPolicyEngine()
|
||||
attributeProvider := &MockAttributeProvider{}
|
||||
|
||||
suite.accessControl, err = NewAccessControlMatrix(suite.config, policyEngine, attributeProvider, suite.auditLogger)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
// TestBasicEncryptionDecryption tests basic encryption and decryption functionality
|
||||
func (suite *RoleCryptoTestSuite) TestBasicEncryptionDecryption() {
|
||||
// Create test context
|
||||
address, err := ucxl.Parse("context://test/basic/encryption")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
testContext := &slurpContext.ContextNode{
|
||||
Path: "/test/basic/encryption",
|
||||
UCXLAddress: address,
|
||||
Summary: "Test context for basic encryption",
|
||||
Purpose: "Testing encryption functionality",
|
||||
Technologies: []string{"go", "crypto"},
|
||||
Tags: []string{"test", "encryption"},
|
||||
Insights: []string{"This is a test insight"},
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.95,
|
||||
EncryptedFor: []string{"backend_developer"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
}
|
||||
|
||||
// Test encryption
|
||||
targetRoles := []string{"backend_developer", "senior_architect"}
|
||||
compartmentTags := []string{"development", "testing"}
|
||||
|
||||
encryptedData, err := suite.roleCrypto.EncryptContextForRoles(testContext, targetRoles, compartmentTags)
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(encryptedData)
|
||||
suite.Equal(address, encryptedData.UCXLAddress)
|
||||
suite.NotEmpty(encryptedData.EncryptedLayers)
|
||||
suite.NotNil(encryptedData.AccessControlMeta)
|
||||
|
||||
// Test decryption
|
||||
decryptedContext, err := suite.roleCrypto.DecryptContextForRole(encryptedData, "backend_developer")
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(decryptedContext)
|
||||
suite.Equal(testContext.Summary, decryptedContext.Summary)
|
||||
suite.Equal(testContext.Purpose, decryptedContext.Purpose)
|
||||
|
||||
// Verify audit logging
|
||||
suite.True(len(suite.auditLogger.accessLogs) > 0)
|
||||
}
|
||||
|
||||
// TestRoleBasedAccess tests role-based access control
|
||||
func (suite *RoleCryptoTestSuite) TestRoleBasedAccess() {
|
||||
address, err := ucxl.Parse("context://test/rbac/access")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
testContext := &slurpContext.ContextNode{
|
||||
Path: "/test/rbac/access",
|
||||
UCXLAddress: address,
|
||||
Summary: "Test context for RBAC",
|
||||
Purpose: "Testing role-based access control",
|
||||
Technologies: []string{"security", "rbac"},
|
||||
Tags: []string{"test", "security"},
|
||||
EncryptedFor: []string{"senior_architect"},
|
||||
AccessLevel: slurpContext.AccessHigh,
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.9,
|
||||
}
|
||||
|
||||
// Encrypt for high-privilege role only
|
||||
encryptedData, err := suite.roleCrypto.EncryptContextForRoles(testContext, []string{"senior_architect"}, []string{"security"})
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Test access with authorized role
|
||||
decryptedContext, err := suite.roleCrypto.DecryptContextForRole(encryptedData, "senior_architect")
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(decryptedContext)
|
||||
|
||||
// Test access with unauthorized role (should fail)
|
||||
_, err = suite.roleCrypto.DecryptContextForRole(encryptedData, "intern")
|
||||
suite.Error(err)
|
||||
suite.Contains(err.Error(), "access denied")
|
||||
}
|
||||
|
||||
// TestMultiLayerEncryption tests multi-layer encryption with different access levels
|
||||
func (suite *RoleCryptoTestSuite) TestMultiLayerEncryption() {
|
||||
address, err := ucxl.Parse("context://test/multilayer/encryption")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
testContext := &slurpContext.ContextNode{
|
||||
Path: "/test/multilayer/encryption",
|
||||
UCXLAddress: address,
|
||||
Summary: "Test context for multi-layer encryption",
|
||||
Purpose: "Testing layered encryption",
|
||||
Technologies: []string{"encryption", "security"},
|
||||
Tags: []string{"test", "multilayer"},
|
||||
Insights: []string{"Multi-layer security insight", "Advanced encryption insight"},
|
||||
EncryptedFor: []string{"backend_developer", "senior_architect", "devops_engineer"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.85,
|
||||
}
|
||||
|
||||
// Encrypt for multiple roles with different access levels
|
||||
targetRoles := []string{"backend_developer", "senior_architect", "devops_engineer"}
|
||||
encryptedData, err := suite.roleCrypto.EncryptContextForRoles(testContext, targetRoles, []string{"development"})
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Verify multiple encryption layers
|
||||
suite.True(len(encryptedData.EncryptedLayers) > 0)
|
||||
suite.NotEmpty(encryptedData.KeyFingerprints)
|
||||
|
||||
// Test decryption with different roles
|
||||
for _, role := range targetRoles {
|
||||
decryptedContext, err := suite.roleCrypto.DecryptContextForRole(encryptedData, role)
|
||||
suite.Require().NoError(err, "Failed to decrypt for role: %s", role)
|
||||
suite.NotNil(decryptedContext)
|
||||
suite.Equal(testContext.Summary, decryptedContext.Summary)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRoleBasedFiltering tests role-specific context filtering
|
||||
func (suite *RoleCryptoTestSuite) TestRoleBasedFiltering() {
|
||||
address, err := ucxl.Parse("context://test/filtering/context")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
testContext := &slurpContext.ContextNode{
|
||||
Path: "/test/filtering/context",
|
||||
UCXLAddress: address,
|
||||
Summary: "Test context for filtering",
|
||||
Purpose: "Testing role-based filtering",
|
||||
Technologies: []string{"frontend", "backend", "database"},
|
||||
Tags: []string{"test", "filtering"},
|
||||
Insights: []string{"Frontend insight", "Backend insight", "Database insight"},
|
||||
EncryptedFor: []string{"frontend_developer", "backend_developer"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.8,
|
||||
}
|
||||
|
||||
encryptedData, err := suite.roleCrypto.EncryptContextForRoles(testContext, []string{"frontend_developer", "backend_developer"}, []string{"development"})
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Test frontend developer access (should get frontend-specific insights)
|
||||
frontendContext, err := suite.roleCrypto.DecryptContextForRole(encryptedData, "frontend_developer")
|
||||
suite.Require().NoError(err)
|
||||
suite.Contains(strings.Join(frontendContext.Insights, " "), "Frontend")
|
||||
|
||||
// Test backend developer access (should get backend-specific insights)
|
||||
backendContext, err := suite.roleCrypto.DecryptContextForRole(encryptedData, "backend_developer")
|
||||
suite.Require().NoError(err)
|
||||
suite.Contains(strings.Join(backendContext.Insights, " "), "Backend")
|
||||
}
|
||||
|
||||
// TestKeyManagement tests key generation and management
|
||||
func (suite *RoleCryptoTestSuite) TestKeyManagement() {
|
||||
// Test key generation
|
||||
keyPair, err := suite.keyManager.GenerateRoleKey("test_role", "age-x25519")
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(keyPair)
|
||||
suite.NotEmpty(keyPair.PublicKey)
|
||||
suite.NotEmpty(keyPair.PrivateKey)
|
||||
suite.True(strings.HasPrefix(keyPair.PublicKey, "age1"))
|
||||
|
||||
// Test key retrieval
|
||||
metadata, err := suite.keyManager.GetKeyMetadata(&KeyFilter{RoleID: "test_role"})
|
||||
suite.Require().NoError(err)
|
||||
suite.True(len(metadata) > 0)
|
||||
|
||||
// Test key integrity verification
|
||||
keyID := fmt.Sprintf("test_role_age-x25519_v%d", keyPair.Version)
|
||||
verificationResult, err := suite.keyManager.VerifyKeyIntegrity(keyID)
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(verificationResult)
|
||||
suite.True(verificationResult.IntegrityOK)
|
||||
}
|
||||
|
||||
// TestKeyRotation tests automatic key rotation
|
||||
func (suite *RoleCryptoTestSuite) TestKeyRotation() {
|
||||
// Create initial key
|
||||
originalKeyPair, err := suite.keyManager.GenerateRoleKey("rotation_test_role", "age-x25519")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Perform key rotation
|
||||
rotationResult, err := suite.keyManager.RotateKey("rotation_test_role", "test_rotation")
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(rotationResult)
|
||||
suite.Contains(rotationResult.RotatedRoles, "rotation_test_role")
|
||||
suite.NotEmpty(rotationResult.NewKeys)
|
||||
suite.NotEmpty(rotationResult.RevokedKeys)
|
||||
|
||||
// Verify new key is different from original
|
||||
newKey := rotationResult.NewKeys["rotation_test_role"]
|
||||
suite.NotEqual(originalKeyPair.PublicKey, string(newKey.KeyData))
|
||||
|
||||
// Verify audit logging
|
||||
suite.True(len(suite.auditLogger.keyRotations) > 0)
|
||||
rotation := suite.auditLogger.keyRotations[len(suite.auditLogger.keyRotations)-1]
|
||||
suite.Equal("test_rotation", rotation.Reason)
|
||||
suite.True(rotation.Success)
|
||||
}
|
||||
|
||||
// TestAccessControlMatrix tests the access control matrix functionality
|
||||
func (suite *RoleCryptoTestSuite) TestAccessControlMatrix() {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create access request
|
||||
request := &AccessRequest{
|
||||
RequestID: "test_request_001",
|
||||
Timestamp: time.Now(),
|
||||
UserID: "test_user",
|
||||
Roles: []string{"backend_developer"},
|
||||
Resource: "context://test/access/resource",
|
||||
ResourceType: "context",
|
||||
Action: "read",
|
||||
ActionType: "data_access",
|
||||
SessionID: "test_session_001",
|
||||
IPAddress: "192.168.1.100",
|
||||
UserAgent: "TestAgent/1.0",
|
||||
Priority: 1,
|
||||
Justification: "Testing access control",
|
||||
Metadata: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Test access evaluation
|
||||
decision, err := suite.accessControl.CheckAccess(ctx, request)
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(decision)
|
||||
suite.Equal(DecisionPermit, decision.Decision)
|
||||
suite.True(decision.ConfidenceScore > 0)
|
||||
suite.True(decision.EvaluationTime > 0)
|
||||
|
||||
// Test unauthorized access
|
||||
unauthorizedRequest := &AccessRequest{
|
||||
RequestID: "test_request_002",
|
||||
Timestamp: time.Now(),
|
||||
UserID: "unauthorized_user",
|
||||
Roles: []string{"intern"},
|
||||
Resource: "context://test/sensitive/resource",
|
||||
ResourceType: "context",
|
||||
Action: "write",
|
||||
ActionType: "data_modification",
|
||||
SessionID: "test_session_002",
|
||||
IPAddress: "192.168.1.200",
|
||||
UserAgent: "TestAgent/1.0",
|
||||
Priority: 1,
|
||||
Justification: "Testing unauthorized access",
|
||||
Metadata: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
unauthorizedDecision, err := suite.accessControl.CheckAccess(ctx, unauthorizedRequest)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(DecisionDeny, unauthorizedDecision.Decision)
|
||||
}
|
||||
|
||||
// TestBypassTokens tests bypass token functionality
|
||||
func (suite *RoleCryptoTestSuite) TestBypassTokens() {
|
||||
// Create bypass token
|
||||
token, err := suite.accessControl.CreateBypassToken(
|
||||
"admin_user",
|
||||
"Emergency maintenance",
|
||||
[]string{"context://emergency/*"},
|
||||
1*time.Hour,
|
||||
5,
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(token)
|
||||
suite.Equal(BypassTokenStatusActive, token.Status)
|
||||
suite.Equal(0, token.UsageCount)
|
||||
|
||||
// Test access with bypass token
|
||||
ctx := context.Background()
|
||||
request := &AccessRequest{
|
||||
RequestID: "bypass_test_001",
|
||||
Timestamp: time.Now(),
|
||||
UserID: "regular_user",
|
||||
Roles: []string{"intern"},
|
||||
Resource: "context://emergency/system",
|
||||
Action: "read",
|
||||
Metadata: map[string]interface{}{"bypass_token": token.TokenID},
|
||||
}
|
||||
|
||||
decision, err := suite.accessControl.CheckAccess(ctx, request)
|
||||
suite.Require().NoError(err)
|
||||
suite.Equal(DecisionPermit, decision.Decision)
|
||||
suite.Contains(decision.Reason, "Bypass token")
|
||||
|
||||
// Verify token usage was recorded
|
||||
suite.Equal(1, token.UsageCount)
|
||||
}
|
||||
|
||||
// TestSecurityMetrics tests security metrics collection
|
||||
func (suite *RoleCryptoTestSuite) TestSecurityMetrics() {
|
||||
// Get role crypto metrics
|
||||
metrics := suite.roleCrypto.GetSecurityMetrics()
|
||||
suite.NotNil(metrics)
|
||||
suite.Contains(metrics, "total_roles")
|
||||
suite.Contains(metrics, "security_score")
|
||||
|
||||
// Get access control metrics
|
||||
acMetrics := suite.accessControl.GetAccessControlMetrics()
|
||||
suite.NotNil(acMetrics)
|
||||
suite.Contains(acMetrics, "total_evaluations")
|
||||
suite.Contains(acMetrics, "enforcement_mode")
|
||||
|
||||
// Get key management security status
|
||||
keyStatus := suite.keyManager.GetSecurityStatus()
|
||||
suite.NotNil(keyStatus)
|
||||
suite.Contains([]string{"healthy", "warning", "degraded", "critical"}, keyStatus.OverallHealth)
|
||||
suite.True(keyStatus.SecurityScore >= 0 && keyStatus.SecurityScore <= 1)
|
||||
}
|
||||
|
||||
// TestComplianceFeatures tests compliance and audit features
|
||||
func (suite *RoleCryptoTestSuite) TestComplianceFeatures() {
|
||||
// Test audit trail retrieval
|
||||
criteria := &AuditCriteria{
|
||||
StartTime: &[]time.Time{time.Now().Add(-1 * time.Hour)}[0],
|
||||
EndTime: &[]time.Time{time.Now()}[0],
|
||||
UserID: "test_user",
|
||||
Limit: 100,
|
||||
}
|
||||
|
||||
auditTrail, err := suite.auditLogger.GetAuditTrail(criteria)
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(auditTrail)
|
||||
|
||||
// Verify audit events have required fields
|
||||
if len(auditTrail) > 0 {
|
||||
event := auditTrail[0]
|
||||
suite.NotEmpty(event.EventID)
|
||||
suite.NotEmpty(event.EventType)
|
||||
suite.NotEmpty(event.UserID)
|
||||
suite.NotZero(event.Timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
// TestErrorHandling tests error handling and edge cases
|
||||
func (suite *RoleCryptoTestSuite) TestErrorHandling() {
|
||||
// Test encryption with invalid context
|
||||
invalidContext := &slurpContext.ContextNode{
|
||||
Path: "", // Invalid: empty path
|
||||
Summary: "", // Invalid: empty summary
|
||||
GeneratedAt: time.Now(),
|
||||
}
|
||||
|
||||
_, err := suite.roleCrypto.EncryptContextForRoles(invalidContext, []string{"backend_developer"}, []string{})
|
||||
suite.Error(err)
|
||||
suite.Contains(err.Error(), "invalid context")
|
||||
|
||||
// Test decryption with invalid role
|
||||
address, _ := ucxl.Parse("context://test/valid/context")
|
||||
validContext := &slurpContext.ContextNode{
|
||||
Path: "/test/valid/context",
|
||||
UCXLAddress: address,
|
||||
Summary: "Valid test context",
|
||||
Purpose: "Testing error handling",
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.8,
|
||||
EncryptedFor: []string{"backend_developer"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
}
|
||||
|
||||
encryptedData, err := suite.roleCrypto.EncryptContextForRoles(validContext, []string{"backend_developer"}, []string{})
|
||||
suite.Require().NoError(err)
|
||||
|
||||
_, err = suite.roleCrypto.DecryptContextForRole(encryptedData, "non_existent_role")
|
||||
suite.Error(err)
|
||||
suite.Contains(err.Error(), "access denied")
|
||||
|
||||
// Test key generation with invalid parameters
|
||||
_, err = suite.keyManager.GenerateRoleKey("", "invalid_type")
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
// TestPerformance tests performance characteristics
|
||||
func (suite *RoleCryptoTestSuite) TestPerformance() {
|
||||
// Create test context
|
||||
address, _ := ucxl.Parse("context://test/performance/benchmark")
|
||||
testContext := &slurpContext.ContextNode{
|
||||
Path: "/test/performance/benchmark",
|
||||
UCXLAddress: address,
|
||||
Summary: "Performance test context",
|
||||
Purpose: "Testing encryption performance",
|
||||
Technologies: []string{"performance", "crypto"},
|
||||
Tags: []string{"test", "benchmark"},
|
||||
Insights: make([]string, 100), // Large insights array
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.9,
|
||||
EncryptedFor: []string{"backend_developer"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
}
|
||||
|
||||
// Fill insights with test data
|
||||
for i := 0; i < 100; i++ {
|
||||
testContext.Insights[i] = fmt.Sprintf("Performance test insight #%d", i+1)
|
||||
}
|
||||
|
||||
// Benchmark encryption
|
||||
start := time.Now()
|
||||
encryptedData, err := suite.roleCrypto.EncryptContextForRoles(testContext, []string{"backend_developer"}, []string{"performance"})
|
||||
encryptionTime := time.Since(start)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.True(encryptionTime < 100*time.Millisecond, "Encryption took too long: %v", encryptionTime)
|
||||
|
||||
// Benchmark decryption
|
||||
start = time.Now()
|
||||
_, err = suite.roleCrypto.DecryptContextForRole(encryptedData, "backend_developer")
|
||||
decryptionTime := time.Since(start)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.True(decryptionTime < 50*time.Millisecond, "Decryption took too long: %v", decryptionTime)
|
||||
|
||||
// Test concurrent operations
|
||||
concurrentOps := 10
|
||||
results := make(chan error, concurrentOps)
|
||||
|
||||
for i := 0; i < concurrentOps; i++ {
|
||||
go func(index int) {
|
||||
address, _ := ucxl.Parse(fmt.Sprintf("context://test/concurrent/%d", index))
|
||||
ctx := &slurpContext.ContextNode{
|
||||
Path: fmt.Sprintf("/test/concurrent/%d", index),
|
||||
UCXLAddress: address,
|
||||
Summary: fmt.Sprintf("Concurrent test context %d", index),
|
||||
Purpose: "Testing concurrent operations",
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.8,
|
||||
EncryptedFor: []string{"backend_developer"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
}
|
||||
|
||||
encrypted, err := suite.roleCrypto.EncryptContextForRoles(ctx, []string{"backend_developer"}, []string{"concurrent"})
|
||||
if err != nil {
|
||||
results <- err
|
||||
return
|
||||
}
|
||||
|
||||
_, err = suite.roleCrypto.DecryptContextForRole(encrypted, "backend_developer")
|
||||
results <- err
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for all operations to complete
|
||||
for i := 0; i < concurrentOps; i++ {
|
||||
err := <-results
|
||||
suite.NoError(err, "Concurrent operation %d failed", i)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSecurityVulnerabilities tests for common security vulnerabilities
|
||||
func (suite *RoleCryptoTestSuite) TestSecurityVulnerabilities() {
|
||||
// Test for timing attacks (encryption should take consistent time)
|
||||
address, _ := ucxl.Parse("context://test/security/timing")
|
||||
baseContext := &slurpContext.ContextNode{
|
||||
Path: "/test/security/timing",
|
||||
UCXLAddress: address,
|
||||
Summary: "Timing attack test",
|
||||
Purpose: "Testing for timing vulnerabilities",
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.8,
|
||||
EncryptedFor: []string{"backend_developer"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
}
|
||||
|
||||
// Measure encryption times for different content sizes
|
||||
var times []time.Duration
|
||||
for i := 0; i < 10; i++ {
|
||||
testContext := *baseContext
|
||||
testContext.Insights = make([]string, i*10) // Varying content size
|
||||
for j := range testContext.Insights {
|
||||
testContext.Insights[j] = fmt.Sprintf("Insight %d", j)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
_, err := suite.roleCrypto.EncryptContextForRoles(&testContext, []string{"backend_developer"}, []string{"timing"})
|
||||
duration := time.Since(start)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
times = append(times, duration)
|
||||
}
|
||||
|
||||
// Check that times don't vary too much (basic timing attack protection)
|
||||
maxTime := times[0]
|
||||
minTime := times[0]
|
||||
for _, t := range times {
|
||||
if t > maxTime {
|
||||
maxTime = t
|
||||
}
|
||||
if t < minTime {
|
||||
minTime = t
|
||||
}
|
||||
}
|
||||
|
||||
// Times should not vary by more than 100% (basic check)
|
||||
variance := float64(maxTime-minTime) / float64(minTime)
|
||||
suite.True(variance < 2.0, "Encryption times vary too much: %v", variance)
|
||||
|
||||
// Test for privilege escalation (lower privilege role shouldn't access higher privilege content)
|
||||
highPrivContext := &slurpContext.ContextNode{
|
||||
Path: "/test/security/privilege",
|
||||
UCXLAddress: address,
|
||||
Summary: "High privilege content",
|
||||
Purpose: "Testing privilege escalation protection",
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.9,
|
||||
EncryptedFor: []string{"senior_architect"},
|
||||
AccessLevel: slurpContext.AccessCritical,
|
||||
}
|
||||
|
||||
encryptedHighPriv, err := suite.roleCrypto.EncryptContextForRoles(highPrivContext, []string{"senior_architect"}, []string{"security"})
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Attempt access with lower privilege role
|
||||
_, err = suite.roleCrypto.DecryptContextForRole(encryptedHighPriv, "intern")
|
||||
suite.Error(err, "Lower privilege role should not access higher privilege content")
|
||||
|
||||
// Test for information leakage in error messages
|
||||
suite.NotContains(err.Error(), "senior_architect", "Error message should not leak role information")
|
||||
suite.NotContains(err.Error(), highPrivContext.Summary, "Error message should not leak content information")
|
||||
}
|
||||
|
||||
// BenchmarkEncryption benchmarks encryption operations
|
||||
func BenchmarkEncryption(b *testing.B) {
|
||||
// Setup
|
||||
config := &config.Config{
|
||||
Agent: config.Agent{ID: "bench_agent", Role: "backend_developer"},
|
||||
}
|
||||
auditLogger := &MockAuditLogger{}
|
||||
ageCrypto := NewAgeCrypto(config)
|
||||
adminKeyManager := NewAdminKeyManager(config, "bench_node")
|
||||
|
||||
roleCrypto, err := NewRoleCrypto(config, ageCrypto, adminKeyManager, auditLogger)
|
||||
require.NoError(b, err)
|
||||
|
||||
address, _ := ucxl.Parse("context://benchmark/encryption")
|
||||
testContext := &slurpContext.ContextNode{
|
||||
Path: "/benchmark/encryption",
|
||||
UCXLAddress: address,
|
||||
Summary: "Benchmark context for encryption testing",
|
||||
Purpose: "Performance testing of encryption operations",
|
||||
Technologies: []string{"crypto", "benchmark"},
|
||||
Tags: []string{"benchmark", "performance"},
|
||||
Insights: []string{"Benchmark insight 1", "Benchmark insight 2"},
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.85,
|
||||
EncryptedFor: []string{"backend_developer"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, err := roleCrypto.EncryptContextForRoles(testContext, []string{"backend_developer"}, []string{"benchmark"})
|
||||
if err != nil {
|
||||
b.Fatalf("Encryption failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkDecryption benchmarks decryption operations
|
||||
func BenchmarkDecryption(b *testing.B) {
|
||||
// Setup
|
||||
config := &config.Config{
|
||||
Agent: config.Agent{ID: "bench_agent", Role: "backend_developer"},
|
||||
}
|
||||
auditLogger := &MockAuditLogger{}
|
||||
ageCrypto := NewAgeCrypto(config)
|
||||
adminKeyManager := NewAdminKeyManager(config, "bench_node")
|
||||
|
||||
roleCrypto, err := NewRoleCrypto(config, ageCrypto, adminKeyManager, auditLogger)
|
||||
require.NoError(b, err)
|
||||
|
||||
address, _ := ucxl.Parse("context://benchmark/decryption")
|
||||
testContext := &slurpContext.ContextNode{
|
||||
Path: "/benchmark/decryption",
|
||||
UCXLAddress: address,
|
||||
Summary: "Benchmark context for decryption testing",
|
||||
Purpose: "Performance testing of decryption operations",
|
||||
Technologies: []string{"crypto", "benchmark"},
|
||||
Tags: []string{"benchmark", "performance"},
|
||||
Insights: []string{"Benchmark insight 1", "Benchmark insight 2"},
|
||||
GeneratedAt: time.Now(),
|
||||
RAGConfidence: 0.85,
|
||||
EncryptedFor: []string{"backend_developer"},
|
||||
AccessLevel: slurpContext.AccessMedium,
|
||||
}
|
||||
|
||||
// Pre-encrypt context for benchmarking decryption
|
||||
encryptedData, err := roleCrypto.EncryptContextForRoles(testContext, []string{"backend_developer"}, []string{"benchmark"})
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, err := roleCrypto.DecryptContextForRole(encryptedData, "backend_developer")
|
||||
if err != nil {
|
||||
b.Fatalf("Decryption failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkAccessControl benchmarks access control evaluation
|
||||
func BenchmarkAccessControl(b *testing.B) {
|
||||
// Setup
|
||||
config := &config.Config{
|
||||
Agent: config.Agent{ID: "bench_agent", Role: "backend_developer"},
|
||||
}
|
||||
auditLogger := &MockAuditLogger{}
|
||||
policyEngine := NewMockPolicyEngine()
|
||||
attributeProvider := &MockAttributeProvider{}
|
||||
|
||||
accessControl, err := NewAccessControlMatrix(config, policyEngine, attributeProvider, auditLogger)
|
||||
require.NoError(b, err)
|
||||
|
||||
ctx := context.Background()
|
||||
request := &AccessRequest{
|
||||
RequestID: "benchmark_request",
|
||||
Timestamp: time.Now(),
|
||||
UserID: "bench_user",
|
||||
Roles: []string{"backend_developer"},
|
||||
Resource: "context://benchmark/access",
|
||||
ResourceType: "context",
|
||||
Action: "read",
|
||||
ActionType: "data_access",
|
||||
SessionID: "bench_session",
|
||||
IPAddress: "192.168.1.100",
|
||||
UserAgent: "BenchmarkAgent/1.0",
|
||||
Priority: 1,
|
||||
Metadata: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, err := accessControl.CheckAccess(ctx, request)
|
||||
if err != nil {
|
||||
b.Fatalf("Access control evaluation failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestMain sets up and tears down the test suite
|
||||
func TestMain(m *testing.M) {
|
||||
// Setup any global test resources here
|
||||
|
||||
// Run tests
|
||||
code := m.Run()
|
||||
|
||||
// Cleanup any global test resources here
|
||||
|
||||
// Exit with the same code as the test run
|
||||
fmt.Printf("Test suite completed with exit code: %d\n", code)
|
||||
}
|
||||
|
||||
// Run the test suite
|
||||
func TestRoleCryptoTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(RoleCryptoTestSuite))
|
||||
}
|
||||
564
pkg/crypto/security_test.go
Normal file
564
pkg/crypto/security_test.go
Normal file
@@ -0,0 +1,564 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"chorus.services/bzzz/pkg/config"
|
||||
)
|
||||
|
||||
// TestSecurityConfig tests SecurityConfig enforcement
|
||||
func TestSecurityConfig(t *testing.T) {
|
||||
// Create temporary audit log file
|
||||
tmpDir, err := ioutil.TempDir("", "bzzz_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("", "bzzz_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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user