mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 15:01:32 +00:00
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:
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -28,7 +29,7 @@ func TestRegisterAgent(t *testing.T) {
|
||||
auditRepo := &mockAuditRepo{Events: []*domain.AuditEvent{}}
|
||||
auditService := NewAuditService(auditRepo)
|
||||
|
||||
issuerRegistry := make(map[string]IssuerConnector)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
@@ -85,7 +86,7 @@ func TestHeartbeat(t *testing.T) {
|
||||
}
|
||||
auditRepo := &mockAuditRepo{}
|
||||
auditService := NewAuditService(auditRepo)
|
||||
issuerRegistry := make(map[string]IssuerConnector)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
@@ -118,7 +119,7 @@ func TestHeartbeat_NotFound(t *testing.T) {
|
||||
}
|
||||
auditRepo := &mockAuditRepo{}
|
||||
auditService := NewAuditService(auditRepo)
|
||||
issuerRegistry := make(map[string]IssuerConnector)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
@@ -175,7 +176,7 @@ func TestGetPendingWork(t *testing.T) {
|
||||
}
|
||||
auditRepo := &mockAuditRepo{}
|
||||
auditService := NewAuditService(auditRepo)
|
||||
issuerRegistry := make(map[string]IssuerConnector)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
@@ -217,7 +218,8 @@ func TestGetPendingWork_OnlyReturnsAgentJobs(t *testing.T) {
|
||||
targetRepo := &mockTargetRepo{Targets: make(map[string]*domain.DeploymentTarget)}
|
||||
auditService := NewAuditService(&mockAuditRepo{})
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, make(map[string]IssuerConnector), nil)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
// Agent A should only see its job
|
||||
jobsA, err := agentService.GetPendingWork(ctx, agentA)
|
||||
@@ -268,7 +270,8 @@ func TestGetPendingWork_EmptyWhenNoJobsForAgent(t *testing.T) {
|
||||
targetRepo := &mockTargetRepo{Targets: make(map[string]*domain.DeploymentTarget)}
|
||||
auditService := NewAuditService(&mockAuditRepo{})
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, make(map[string]IssuerConnector), nil)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
jobs, err := agentService.GetPendingWork(ctx, agentA)
|
||||
if err != nil {
|
||||
@@ -302,7 +305,8 @@ func TestGetPendingWork_DeploymentAndCSR_Scoped(t *testing.T) {
|
||||
targetRepo := &mockTargetRepo{Targets: make(map[string]*domain.DeploymentTarget)}
|
||||
auditService := NewAuditService(&mockAuditRepo{})
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, make(map[string]IssuerConnector), nil)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
jobs, err := agentService.GetPendingWork(ctx, agentA)
|
||||
if err != nil {
|
||||
@@ -350,7 +354,7 @@ func TestReportJobStatus(t *testing.T) {
|
||||
}
|
||||
auditRepo := &mockAuditRepo{Events: []*domain.AuditEvent{}}
|
||||
auditService := NewAuditService(auditRepo)
|
||||
issuerRegistry := make(map[string]IssuerConnector)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
@@ -409,7 +413,7 @@ func TestMarkStaleAgentsOffline(t *testing.T) {
|
||||
}
|
||||
auditRepo := &mockAuditRepo{}
|
||||
auditService := NewAuditService(auditRepo)
|
||||
issuerRegistry := make(map[string]IssuerConnector)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
@@ -475,7 +479,8 @@ func TestSubmitCSR(t *testing.T) {
|
||||
NotAfter: now.AddDate(1, 0, 0),
|
||||
},
|
||||
}
|
||||
issuerRegistry := map[string]IssuerConnector{"iss-local": issuerConnector}
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
issuerRegistry.Set("iss-local", issuerConnector)
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
@@ -524,7 +529,7 @@ func TestSubmitCSR_EmptyCSR(t *testing.T) {
|
||||
}
|
||||
auditRepo := &mockAuditRepo{}
|
||||
auditService := NewAuditService(auditRepo)
|
||||
issuerRegistry := make(map[string]IssuerConnector)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
@@ -572,7 +577,7 @@ func TestListAgents(t *testing.T) {
|
||||
}
|
||||
auditRepo := &mockAuditRepo{}
|
||||
auditService := NewAuditService(auditRepo)
|
||||
issuerRegistry := make(map[string]IssuerConnector)
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
|
||||
agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user