Some checks failed
Build Pipeline / Build Binaries (push) Failing after 3m39s
Build Pipeline / Build Docker Images (push) Has been skipped
Build Pipeline / Sign HIPAA Config (push) Has been skipped
Build Pipeline / Generate SLSA Provenance (push) Has been skipped
Checkout test / test (push) Successful in 6s
CI Pipeline / Test (ubuntu-latest on self-hosted) (push) Failing after 1s
CI Pipeline / Dev Compose Smoke Test (push) Has been skipped
CI Pipeline / Security Scan (push) Has been skipped
CI Pipeline / Test Scripts (push) Has been skipped
CI Pipeline / Test Native Libraries (push) Has been skipped
CI Pipeline / Native Library Build Matrix (push) Has been skipped
Contract Tests / Spec Drift Detection (push) Failing after 11s
Contract Tests / API Contract Tests (push) Has been skipped
Deploy API Docs / Build API Documentation (push) Failing after 5s
Deploy API Docs / Deploy to GitHub Pages (push) Has been skipped
Documentation / build-and-publish (push) Failing after 40s
Test Matrix / test-native-vs-pure (cgo) (push) Failing after 14s
Test Matrix / test-native-vs-pure (native) (push) Failing after 35s
Test Matrix / test-native-vs-pure (pure) (push) Failing after 18s
CI Pipeline / Trigger Build Workflow (push) Failing after 1s
Build CLI with Embedded SQLite / build (arm64, aarch64-linux) (push) Has been cancelled
Build CLI with Embedded SQLite / build (x86_64, x86_64-linux) (push) Has been cancelled
Build CLI with Embedded SQLite / build-macos (arm64) (push) Has been cancelled
Build CLI with Embedded SQLite / build-macos (x86_64) (push) Has been cancelled
Security Scan / Security Analysis (push) Has been cancelled
Security Scan / Native Library Security (push) Has been cancelled
Verification & Maintenance / V.1 - Schema Drift Detection (push) Has been cancelled
Verification & Maintenance / V.4 - Custom Go Vet Analyzers (push) Has been cancelled
Verification & Maintenance / V.7 - Audit Chain Integrity (push) Has been cancelled
Verification & Maintenance / V.6 - Extended Security Scanning (push) Has been cancelled
Verification & Maintenance / V.10 - OpenSSF Scorecard (push) Has been cancelled
Verification & Maintenance / Verification Summary (push) Has been cancelled
- Introduce audit, plugin, and scheduler API handlers - Add spec_embed.go for OpenAPI spec embedding - Create modular build scripts (cli, go, native, cross-platform) - Add deployment cleanup and health-check utilities - New ADRs: hot reload, audit store, SSE updates, RBAC, caching, offline mode, KMS regions, tenant offboarding - Add KMS configuration schema and worker variants - Include KMS benchmark tests
279 lines
7.1 KiB
Go
279 lines
7.1 KiB
Go
package benchmarks
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/crypto"
|
|
"github.com/jfraeys/fetch_ml/internal/crypto/kms"
|
|
)
|
|
|
|
// BenchmarkEncryptArtifact measures the full encryption pipeline performance.
|
|
// Per ADR-012: Total overhead should be <10ms for MemoryProvider.
|
|
func BenchmarkEncryptArtifact(b *testing.B) {
|
|
tkm := crypto.NewTestTenantKeyManager(nil)
|
|
|
|
// Provision a test tenant
|
|
hierarchy, err := tkm.ProvisionTenant("bench-tenant")
|
|
if err != nil {
|
|
b.Fatalf("Failed to provision tenant: %v", err)
|
|
}
|
|
|
|
// Test data - 1KB payload (typical model weights chunk)
|
|
plaintext := make([]byte, 1024)
|
|
for i := range plaintext {
|
|
plaintext[i] = byte(i % 256)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
_, err := tkm.EncryptArtifact("bench-tenant", "artifact-1", hierarchy.KMSKeyID, plaintext)
|
|
if err != nil {
|
|
b.Fatalf("Encrypt failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkDecryptArtifact measures the full decryption pipeline performance.
|
|
// Per ADR-012: Total overhead should be <10ms for MemoryProvider.
|
|
func BenchmarkDecryptArtifact(b *testing.B) {
|
|
tkm := crypto.NewTestTenantKeyManager(nil)
|
|
|
|
hierarchy, err := tkm.ProvisionTenant("bench-tenant")
|
|
if err != nil {
|
|
b.Fatalf("Failed to provision tenant: %v", err)
|
|
}
|
|
|
|
plaintext := make([]byte, 1024)
|
|
for i := range plaintext {
|
|
plaintext[i] = byte(i % 256)
|
|
}
|
|
|
|
// Pre-encrypt data
|
|
encrypted, err := tkm.EncryptArtifact("bench-tenant", "artifact-1", hierarchy.KMSKeyID, plaintext)
|
|
if err != nil {
|
|
b.Fatalf("Pre-encryption failed: %v", err)
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
_, err := tkm.DecryptArtifact(encrypted, hierarchy.KMSKeyID)
|
|
if err != nil {
|
|
b.Fatalf("Decrypt failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkMemoryProvider_Encrypt measures baseline encryption without network overhead.
|
|
// This establishes the theoretical minimum for KMS operations.
|
|
func BenchmarkMemoryProvider_Encrypt(b *testing.B) {
|
|
provider := kms.NewMemoryProvider()
|
|
defer provider.Close()
|
|
|
|
cache := kms.NewDEKCache(kms.DefaultCacheConfig())
|
|
defer cache.Clear()
|
|
|
|
config := kms.Config{
|
|
Provider: kms.ProviderTypeMemory,
|
|
Cache: kms.DefaultCacheConfig(),
|
|
}
|
|
|
|
tkm := crypto.NewTenantKeyManager(provider, cache, config, nil)
|
|
|
|
hierarchy, err := tkm.ProvisionTenant("bench-tenant")
|
|
if err != nil {
|
|
b.Fatalf("Failed to provision tenant: %v", err)
|
|
}
|
|
|
|
plaintext := make([]byte, 1024)
|
|
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
_, err := tkm.EncryptArtifact("bench-tenant", "artifact-1", hierarchy.KMSKeyID, plaintext)
|
|
if err != nil {
|
|
b.Fatalf("Encrypt failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkCacheHit verifies cached DEKs provide <10ms overhead.
|
|
func BenchmarkCacheHit(b *testing.B) {
|
|
tkm := crypto.NewTestTenantKeyManager(nil)
|
|
|
|
hierarchy, err := tkm.ProvisionTenant("bench-tenant")
|
|
if err != nil {
|
|
b.Fatalf("Failed to provision tenant: %v", err)
|
|
}
|
|
|
|
plaintext := make([]byte, 1024)
|
|
|
|
// First encrypt to populate cache
|
|
encrypted, err := tkm.EncryptArtifact("bench-tenant", "cached-artifact", hierarchy.KMSKeyID, plaintext)
|
|
if err != nil {
|
|
b.Fatalf("Pre-encryption failed: %v", err)
|
|
}
|
|
|
|
// First decrypt to populate DEK cache
|
|
_, err = tkm.DecryptArtifact(encrypted, hierarchy.KMSKeyID)
|
|
if err != nil {
|
|
b.Fatalf("First decrypt failed: %v", err)
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
_, err := tkm.DecryptArtifact(encrypted, hierarchy.KMSKeyID)
|
|
if err != nil {
|
|
b.Fatalf("Decrypt failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkKeyRotation measures key rotation overhead.
|
|
func BenchmarkKeyRotation(b *testing.B) {
|
|
tkm := crypto.NewTestTenantKeyManager(nil)
|
|
|
|
hierarchy, err := tkm.ProvisionTenant("bench-tenant")
|
|
if err != nil {
|
|
b.Fatalf("Failed to provision tenant: %v", err)
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
// Rotate key
|
|
newHierarchy, err := tkm.RotateTenantKey("bench-tenant", hierarchy)
|
|
if err != nil {
|
|
b.Fatalf("Rotation failed: %v", err)
|
|
}
|
|
hierarchy = newHierarchy
|
|
}
|
|
}
|
|
|
|
// BenchmarkEncryptArtifact_LargePayload measures encryption with larger payloads.
|
|
func BenchmarkEncryptArtifact_LargePayload(b *testing.B) {
|
|
tkm := crypto.NewTestTenantKeyManager(nil)
|
|
|
|
hierarchy, err := tkm.ProvisionTenant("bench-tenant")
|
|
if err != nil {
|
|
b.Fatalf("Failed to provision tenant: %v", err)
|
|
}
|
|
|
|
// 1MB payload
|
|
plaintext := make([]byte, 1024*1024)
|
|
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
_, err := tkm.EncryptArtifact("bench-tenant", "large-artifact", hierarchy.KMSKeyID, plaintext)
|
|
if err != nil {
|
|
b.Fatalf("Encrypt failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkParallelEncrypt measures concurrent encryption performance.
|
|
func BenchmarkParallelEncrypt(b *testing.B) {
|
|
tkm := crypto.NewTestTenantKeyManager(nil)
|
|
|
|
hierarchy, err := tkm.ProvisionTenant("bench-tenant")
|
|
if err != nil {
|
|
b.Fatalf("Failed to provision tenant: %v", err)
|
|
}
|
|
|
|
plaintext := make([]byte, 1024)
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
i := 0
|
|
for pb.Next() {
|
|
artifactID := "parallel-artifact-" + string(rune('0'+i%10))
|
|
_, err := tkm.EncryptArtifact("bench-tenant", artifactID, hierarchy.KMSKeyID, plaintext)
|
|
if err != nil {
|
|
b.Fatalf("Encrypt failed: %v", err)
|
|
}
|
|
i++
|
|
}
|
|
})
|
|
}
|
|
|
|
// VerifyPerformanceRequirement runs a quick sanity check for the <10ms requirement.
|
|
// This is not a benchmark but a verification that typical operations complete within limits.
|
|
func TestEncryptPerformance_10msRequirement(t *testing.T) {
|
|
tkm := crypto.NewTestTenantKeyManager(nil)
|
|
|
|
hierarchy, err := tkm.ProvisionTenant("perf-test-tenant")
|
|
if err != nil {
|
|
t.Fatalf("Failed to provision tenant: %v", err)
|
|
}
|
|
|
|
plaintext := make([]byte, 1024)
|
|
|
|
// Warm up
|
|
for range 10 {
|
|
_, _ = tkm.EncryptArtifact("perf-test-tenant", "warmup", hierarchy.KMSKeyID, plaintext)
|
|
}
|
|
|
|
// Measure 100 operations
|
|
start := time.Now()
|
|
for i := 0; i < 100; i++ {
|
|
_, err := tkm.EncryptArtifact("perf-test-tenant", "perf-test", hierarchy.KMSKeyID, plaintext)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt failed: %v", err)
|
|
}
|
|
}
|
|
elapsed := time.Since(start)
|
|
|
|
avgPerOp := elapsed / 100
|
|
if avgPerOp > 10*time.Millisecond {
|
|
t.Errorf("Average encrypt time %v exceeds 10ms requirement", avgPerOp)
|
|
}
|
|
|
|
t.Logf("Average encrypt time: %v (requirement: <10ms)", avgPerOp)
|
|
}
|
|
|
|
// TestDecryptPerformance_10msRequirement verifies decrypt completes within 10ms.
|
|
func TestDecryptPerformance_10msRequirement(t *testing.T) {
|
|
tkm := crypto.NewTestTenantKeyManager(nil)
|
|
|
|
hierarchy, err := tkm.ProvisionTenant("perf-test-tenant")
|
|
if err != nil {
|
|
t.Fatalf("Failed to provision tenant: %v", err)
|
|
}
|
|
|
|
plaintext := make([]byte, 1024)
|
|
|
|
// Pre-encrypt
|
|
encrypted, err := tkm.EncryptArtifact("perf-test-tenant", "perf-test", hierarchy.KMSKeyID, plaintext)
|
|
if err != nil {
|
|
t.Fatalf("Pre-encryption failed: %v", err)
|
|
}
|
|
|
|
// Warm up cache
|
|
for range 10 {
|
|
_, _ = tkm.DecryptArtifact(encrypted, hierarchy.KMSKeyID)
|
|
}
|
|
|
|
// Measure 100 operations with cache
|
|
start := time.Now()
|
|
for range 10 {
|
|
_, err := tkm.DecryptArtifact(encrypted, hierarchy.KMSKeyID)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt failed: %v", err)
|
|
}
|
|
}
|
|
elapsed := time.Since(start)
|
|
|
|
avgPerOp := elapsed / 100
|
|
if avgPerOp > 10*time.Millisecond {
|
|
t.Errorf("Average decrypt time %v exceeds 10ms requirement", avgPerOp)
|
|
}
|
|
|
|
t.Logf("Average decrypt time: %v (requirement: <10ms)", avgPerOp)
|
|
}
|