feat(M34): dynamic issuer configuration with encrypted config storage

Replace static env-var-based issuer wiring with GUI-driven dynamic
configuration stored encrypted in PostgreSQL. Operators can now
configure, test, enable/disable, and manage issuers from the dashboard
without restarting the server.

Key changes:
- AES-256-GCM encryption for sensitive issuer config at rest (PBKDF2
  key derivation with 100k iterations)
- Dynamic IssuerRegistry with sync.RWMutex replacing static map
- Connector factory pattern (issuerfactory.NewFromConfig) replacing
  140 lines of static wiring in main.go
- Migration 000009: encrypted_config, last_tested_at, test_status,
  source columns on issuers table
- Env var seeding on first boot with ON CONFLICT DO NOTHING
- Registry Rebuild() for atomic map swap after CRUD operations
- Issuer type validation against domain constants on Create
- Audit trail for test connection results
- Conditional seeding for step-ca/OpenSSL (only when env vars set)
- GUI: source badge, connection test status on issuer detail page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Shankar
2026-04-04 00:20:13 -04:00
parent 7a24cc7b26
commit 6c3bc88d3d
36 changed files with 1859 additions and 361 deletions
+7 -3
View File
@@ -2,6 +2,7 @@ package service
import (
"context"
"log/slog"
"testing"
"time"
@@ -66,6 +67,7 @@ func TestRenewalService_ProcessWithCancelledContext(t *testing.T) {
notifierRegistry: make(map[string]Notifier),
}
issuerRegistry := NewIssuerRegistry(slog.Default())
renewalSvc := NewRenewalService(
mockCertRepo,
mockJobRepo,
@@ -73,7 +75,7 @@ func TestRenewalService_ProcessWithCancelledContext(t *testing.T) {
mockProfileRepo,
mockAuditSvc,
mockNotifSvc,
make(map[string]IssuerConnector),
issuerRegistry,
"agent",
)
@@ -162,13 +164,14 @@ func TestAgentService_HeartbeatWithCancelledContext(t *testing.T) {
Hostname: "localhost",
})
issuerRegistry := NewIssuerRegistry(slog.Default())
agentSvc := NewAgentService(
mockAgentRepo,
nil, // certRepo
nil, // jobRepo
nil, // targetRepo
nil, // auditService
make(map[string]IssuerConnector),
issuerRegistry,
nil, // renewalService
)
@@ -212,13 +215,14 @@ func TestAgentService_HeartbeatWithDeadlineExceeded(t *testing.T) {
Hostname: "localhost",
})
issuerRegistry := NewIssuerRegistry(slog.Default())
agentSvc := NewAgentService(
mockAgentRepo,
nil, // certRepo
nil, // jobRepo
nil, // targetRepo
nil, // auditService
make(map[string]IssuerConnector),
issuerRegistry,
nil, // renewalService
)