mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-14 23:28:52 +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:
@@ -0,0 +1,87 @@
|
||||
package issuerfactory
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer"
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer/acme"
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer/digicert"
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer/googlecas"
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer/local"
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer/openssl"
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer/sectigo"
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer/stepca"
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer/vault"
|
||||
)
|
||||
|
||||
// NewFromConfig instantiates an issuer connector from its type string and config JSON.
|
||||
// The config JSON keys use snake_case matching the connector Config struct json tags.
|
||||
// This replaces the manual wiring in cmd/server/main.go.
|
||||
func NewFromConfig(issuerType string, configJSON json.RawMessage, logger *slog.Logger) (issuer.Connector, error) {
|
||||
if len(configJSON) == 0 {
|
||||
configJSON = []byte("{}")
|
||||
}
|
||||
|
||||
switch issuerType {
|
||||
case "local", "GenericCA":
|
||||
var cfg local.Config
|
||||
if err := json.Unmarshal(configJSON, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("invalid Local CA config: %w", err)
|
||||
}
|
||||
return local.New(&cfg, logger), nil
|
||||
|
||||
case "ACME":
|
||||
var cfg acme.Config
|
||||
if err := json.Unmarshal(configJSON, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("invalid ACME config: %w", err)
|
||||
}
|
||||
return acme.New(&cfg, logger), nil
|
||||
|
||||
case "StepCA":
|
||||
var cfg stepca.Config
|
||||
if err := json.Unmarshal(configJSON, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("invalid step-ca config: %w", err)
|
||||
}
|
||||
return stepca.New(&cfg, logger), nil
|
||||
|
||||
case "OpenSSL":
|
||||
var cfg openssl.Config
|
||||
if err := json.Unmarshal(configJSON, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("invalid OpenSSL config: %w", err)
|
||||
}
|
||||
return openssl.New(&cfg, logger), nil
|
||||
|
||||
case "VaultPKI":
|
||||
var cfg vault.Config
|
||||
if err := json.Unmarshal(configJSON, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("invalid Vault PKI config: %w", err)
|
||||
}
|
||||
return vault.New(&cfg, logger), nil
|
||||
|
||||
case "DigiCert":
|
||||
var cfg digicert.Config
|
||||
if err := json.Unmarshal(configJSON, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("invalid DigiCert config: %w", err)
|
||||
}
|
||||
return digicert.New(&cfg, logger), nil
|
||||
|
||||
case "Sectigo":
|
||||
var cfg sectigo.Config
|
||||
if err := json.Unmarshal(configJSON, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("invalid Sectigo config: %w", err)
|
||||
}
|
||||
return sectigo.New(&cfg, logger), nil
|
||||
|
||||
case "GoogleCAS":
|
||||
var cfg googlecas.Config
|
||||
if err := json.Unmarshal(configJSON, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("invalid Google CAS config: %w", err)
|
||||
}
|
||||
return googlecas.New(&cfg, logger), nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown issuer type: %q", issuerType)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user