- Add API functions for fetching encryption status, rotating keys, retrieving rotation history, and validating key configuration. - Create custom hooks for managing encryption status and key operations. - Develop the EncryptionManagement page with UI components for displaying status, actions, and rotation history. - Implement confirmation dialog for key rotation and handle loading states and error messages. - Add tests for the EncryptionManagement component to ensure functionality and error handling.
42 KiB
DNS Future Features Implementation Plan
Version: 1.0.0 Created: January 3, 2026 Status: Ready for Implementation Source: dns_challenge_future_features.md
Table of Contents
- Executive Summary
- Phase Breakdown
- Feature 1: Audit Logging for Credential Operations
- Feature 2: Key Rotation Automation
- Feature 3: Multi-Credential per Provider
- Feature 4: DNS Provider Auto-Detection
- Feature 5: Custom DNS Provider Plugins
- Code Sharing and Pattern Reuse
- Risk Assessment
- Testing Strategy
Executive Summary
This document provides a detailed implementation plan for five DNS Challenge enhancement features. Each feature includes exact file locations, code patterns to follow, dependencies, and acceptance criteria.
Implementation Order (Recommended)
| Phase | Feature | Priority | Estimated Hours | Dependencies |
|---|---|---|---|---|
| 1 | Audit Logging | P0 | 8-12 | None |
| 2 | Key Rotation Automation | P1 | 16-20 | Audit Logging |
| 3 | Multi-Credential per Provider | P1 | 12-16 | Audit Logging |
| 4 | DNS Provider Auto-Detection | P2 | 6-8 | None |
| 5 | Custom DNS Provider Plugins | P3 | 20-24 | All above |
Existing Code Patterns to Follow
Based on codebase analysis:
- Models: Follow pattern in backend/internal/models/dns_provider.go
- Services: Follow pattern in backend/internal/services/dns_provider_service.go
- Handlers: Follow pattern in backend/internal/api/handlers/dns_provider_handler.go
- API Client: Follow pattern in frontend/src/api/dnsProviders.ts
- React Hooks: Follow pattern in frontend/src/hooks/useDNSProviders.ts
Phase Breakdown
Phase 1: Security Foundation (Week 1)
Feature: Audit Logging for Credential Operations
Rationale: Establishes compliance foundation. The existing SecurityAudit model (backend/internal/models/security_audit.go) already exists but needs extension. The SecurityService.LogAudit() method (backend/internal/services/security_service.go#L163) is already implemented.
Blocking: None - can start immediately
Phase 2: Security Hardening (Week 2)
Feature: Key Rotation Automation
Rationale: Critical for security posture. Depends on Audit Logging to track rotation events.
Blocking: Audit Logging must be complete to track rotation events
Phase 3: Advanced Features (Week 3)
Feature: Multi-Credential per Provider
Rationale: Enables zone-level security isolation. Can be implemented in parallel with Phase 2 if resources allow.
Blocking: Audit Logging (to track credential operations per zone)
Phase 4: UX Enhancement (Week 3-4)
Feature: DNS Provider Auto-Detection
Rationale: Independent feature that improves user experience. Can be implemented in parallel with Phase 3.
Blocking: None - independent feature
Phase 5: Extensibility (Week 4-5)
Feature: Custom DNS Provider Plugins
Rationale: Most complex feature. Benefits from mature audit/rotation systems and lessons learned from previous phases.
Blocking: All above features should be stable before adding plugin complexity
Feature 1: Audit Logging for Credential Operations
Overview
- Priority: P0 - Critical
- Estimated Time: 8-12 hours
- Dependencies: None
File Inventory
Backend Files to Modify
| File | Changes |
|---|---|
| backend/internal/models/security_audit.go | Extend SecurityAudit struct with new fields |
| backend/internal/services/dns_provider_service.go | Add audit logging to CRUD operations |
| backend/internal/services/security_service.go | Add ListAuditLogs() method with filtering |
| backend/internal/api/routes/routes.go | Register new audit log routes |
Backend Files to Create
| File | Purpose |
|---|---|
backend/internal/api/handlers/audit_log_handler.go |
REST API handler for audit logs |
backend/internal/api/handlers/audit_log_handler_test.go |
Unit tests (85% coverage target) |
backend/internal/services/audit_log_service.go |
Service layer for audit log querying |
backend/internal/services/audit_log_service_test.go |
Unit tests (85% coverage target) |
Frontend Files to Create
| File | Purpose |
|---|---|
frontend/src/api/auditLogs.ts |
API client functions |
frontend/src/hooks/useAuditLogs.ts |
React Query hooks |
frontend/src/pages/AuditLogs.tsx |
Audit logs page component |
frontend/src/pages/__tests__/AuditLogs.test.tsx |
Component tests |
Database Migration
Pattern: GORM AutoMigrate (follows existing codebase pattern)
- Update the
SecurityAuditmodel in backend/internal/models/security_audit.go with new fields and indexes - Run
db.AutoMigrate(&models.SecurityAudit{})in the application initialization code - GORM will automatically add missing columns and indexes
Note: The project does not use standalone SQL migration files. All schema changes are managed through GORM's AutoMigrate feature.
Model Extension
File: backend/internal/models/security_audit.go
// SecurityAudit records admin actions or important changes related to security.
type SecurityAudit struct {
ID uint `json:"id" gorm:"primaryKey"`
UUID string `json:"uuid" gorm:"uniqueIndex"`
Actor string `json:"actor" gorm:"index"` // User ID or "system"
Action string `json:"action"` // e.g., "dns_provider_create"
EventCategory string `json:"event_category" gorm:"index"` // "dns_provider", "certificate", etc.
ResourceID *uint `json:"resource_id,omitempty"` // DNSProvider.ID
ResourceUUID string `json:"resource_uuid,omitempty" gorm:"index"` // DNSProvider.UUID
Details string `json:"details" gorm:"type:text"` // JSON blob with event metadata
IPAddress string `json:"ip_address,omitempty"` // Request originator IP
UserAgent string `json:"user_agent,omitempty"` // Browser/API client
CreatedAt time.Time `json:"created_at" gorm:"index"`
}
API Endpoints
| Method | Endpoint | Handler | Description |
|---|---|---|---|
GET |
/api/v1/audit-logs |
AuditLogHandler.List |
List with pagination and filtering |
GET |
/api/v1/audit-logs/:uuid |
AuditLogHandler.Get |
Get single audit event |
GET |
/api/v1/dns-providers/:id/audit-logs |
AuditLogHandler.ListByProvider |
Provider-specific audit history |
DELETE |
/api/v1/audit-logs/cleanup |
AuditLogHandler.Cleanup |
Manual cleanup (admin only) |
Audit Events to Implement
| Event Action | Trigger Location | Details JSON |
|---|---|---|
dns_provider_create |
DNSProviderService.Create() |
{"name","type","is_default"} |
dns_provider_update |
DNSProviderService.Update() |
{"changed_fields","old_values","new_values"} |
dns_provider_delete |
DNSProviderService.Delete() |
{"name","type","had_credentials"} |
credential_test |
DNSProviderService.Test() |
{"provider_name","test_result","error"} |
credential_decrypt |
DNSProviderService.GetDecryptedCredentials() |
{"purpose","success"} |
Definition of Done
- SecurityAudit model extended with new fields and index tags
- GORM AutoMigrate adds columns and indexes successfully
- All DNS provider CRUD operations logged via buffered channel (capacity: 100)
- Credential decryption events logged asynchronously
- SecurityService extended with
ListAuditLogs()filtering/pagination methods - API endpoints return paginated audit logs
- Frontend AuditLogs page displays table with filters
- Route added to App.tsx router configuration
- Navigation link integrated in main menu
- Export to CSV/JSON functionality works
- Retention policy configurable (default: 90 days)
- Background cleanup job removes old logs
- Backend tests achieve ≥85% coverage
- Frontend tests achieve ≥85% coverage
- API documentation updated
- No performance regression (async audit logging <1ms overhead)
Feature 2: Key Rotation Automation
Overview
- Priority: P1
- Estimated Time: 16-20 hours
- Dependencies: Audit Logging (to track rotation events)
File Inventory
Backend Files to Create
| File | Purpose |
|---|---|
backend/internal/crypto/rotation_service.go |
Key rotation logic with multi-key support |
backend/internal/crypto/rotation_service_test.go |
Unit tests (85% coverage target) |
backend/internal/api/handlers/encryption_handler.go |
Admin API for key management |
backend/internal/api/handlers/encryption_handler_test.go |
Unit tests |
Backend Files to Modify
| File | Changes |
|---|---|
| backend/internal/crypto/encryption.go | Add key version tracking capability |
| backend/internal/models/dns_provider.go | Add KeyVersion field |
| backend/internal/services/dns_provider_service.go | Use RotationService for encryption/decryption |
| backend/internal/api/routes/routes.go | Register admin encryption routes |
Frontend Files to Create
| File | Purpose |
|---|---|
frontend/src/api/encryption.ts |
API client for encryption management |
frontend/src/hooks/useEncryption.ts |
React Query hooks |
frontend/src/pages/EncryptionManagement.tsx |
Admin page for key management |
frontend/src/pages/__tests__/EncryptionManagement.test.tsx |
Component tests |
Database Migration
Pattern: GORM AutoMigrate (follows existing codebase pattern)
- Add
KeyVersionfield toDNSProvidermodel with gorm index tag - Run
db.AutoMigrate(&models.DNSProvider{})in the application initialization code - GORM will automatically add the
key_versioncolumn and index
Note: Key version tracking is managed purely through environment variables. No encryption_keys table is needed. This aligns with 12-factor principles and simplifies the architecture.
Environment Variable Schema
Version tracking is managed purely via environment variables - no database table needed.
# Current encryption key (required) - Version 1 by default
CHARON_ENCRYPTION_KEY=<base64-encoded-32-byte-key>
# During rotation: new key to encrypt with (becomes Version 2)
CHARON_ENCRYPTION_KEY_V2=<base64-encoded-32-byte-key>
# Legacy keys for decryption only (up to 10 versions supported)
CHARON_ENCRYPTION_KEY_V1=<old-key> # Previous version
CHARON_ENCRYPTION_KEY_V3=<even-older-key> # If rotating multiple times
Rotation Flow:
- Set
CHARON_ENCRYPTION_KEY_V2with new key - Restart application (loads both keys)
- Trigger
/api/v1/admin/encryption/rotateendpoint - All credentials re-encrypted with V2
- Rename:
CHARON_ENCRYPTION_KEY_V2→CHARON_ENCRYPTION_KEY, old key →CHARON_ENCRYPTION_KEY_V1 - Restart application
RotationService Interface
type RotationService interface {
// DecryptWithVersion decrypts using the appropriate key version
DecryptWithVersion(ciphertextB64 string, version int) ([]byte, error)
// EncryptWithCurrentKey encrypts with current (or next during rotation) key
EncryptWithCurrentKey(plaintext []byte) (ciphertext string, version int, err error)
// RotateAllCredentials re-encrypts all credentials with new key
RotateAllCredentials(ctx context.Context) (*RotationResult, error)
// GetStatus returns current rotation status
GetStatus() *RotationStatus
// ValidateKeyConfiguration checks all configured keys
ValidateKeyConfiguration() error
}
type RotationResult struct {
TotalProviders int `json:"total_providers"`
SuccessCount int `json:"success_count"`
FailureCount int `json:"failure_count"`
FailedProviders []uint `json:"failed_providers,omitempty"`
Duration string `json:"duration"`
NewKeyVersion int `json:"new_key_version"`
}
type RotationStatus struct {
CurrentVersion int `json:"current_version"`
NextKeyConfigured bool `json:"next_key_configured"`
LegacyKeyCount int `json:"legacy_key_count"`
ProvidersOnCurrentVersion int `json:"providers_on_current_version"`
ProvidersOnOlderVersions int `json:"providers_on_older_versions"`
}
API Endpoints
| Method | Endpoint | Handler | Description |
|---|---|---|---|
GET |
/api/v1/admin/encryption/status |
EncryptionHandler.GetStatus |
Current key version, rotation status |
POST |
/api/v1/admin/encryption/rotate |
EncryptionHandler.Rotate |
Trigger credential re-encryption |
GET |
/api/v1/admin/encryption/history |
EncryptionHandler.GetHistory |
Key rotation audit log |
POST |
/api/v1/admin/encryption/validate |
EncryptionHandler.Validate |
Validate key configuration |
Definition of Done
- RotationService created with multi-key support
- Legacy key loading from environment variables works
- Decrypt falls back to older key versions automatically
- Encrypt uses current (or NEXT during rotation) key
- RotateAllCredentials re-encrypts all providers
- Rotation progress tracking (% complete)
- Audit events logged for all rotation operations
- Admin UI shows rotation status and controls
- Zero-downtime rotation verified in testing
- Rollback procedure documented
- Backend tests achieve ≥85% coverage
- Frontend tests achieve ≥85% coverage
- Operations guide documents rotation procedure
Feature 3: Multi-Credential per Provider
Overview
- Priority: P1
- Estimated Time: 12-16 hours
- Dependencies: Audit Logging
File Inventory
Backend Files to Create
| File | Purpose |
|---|---|
backend/internal/models/dns_provider_credential.go |
New credential model |
backend/internal/models/dns_provider_credential_test.go |
Model tests |
backend/internal/services/credential_service.go |
CRUD for zone-specific credentials |
backend/internal/services/credential_service_test.go |
Unit tests (85% coverage target) |
backend/internal/api/handlers/credential_handler.go |
REST API for credentials |
backend/internal/api/handlers/credential_handler_test.go |
Unit tests |
Backend Files to Modify
| File | Changes |
|---|---|
| backend/internal/models/dns_provider.go | Add UseMultiCredentials flag and Credentials relation |
| backend/internal/services/dns_provider_service.go | Add GetCredentialForDomain() method |
| backend/internal/api/routes/routes.go | Register credential routes |
Frontend Files to Create
| File | Purpose |
|---|---|
frontend/src/api/credentials.ts |
API client for credentials |
frontend/src/hooks/useCredentials.ts |
React Query hooks |
frontend/src/components/CredentialManager.tsx |
Modal for managing credentials |
frontend/src/components/__tests__/CredentialManager.test.tsx |
Component tests |
Frontend Files to Modify
| File | Changes |
|---|---|
frontend/src/pages/DNSProviders.tsx |
Add multi-credential toggle and management UI |
Database Migration
Pattern: GORM AutoMigrate (follows existing codebase pattern)
- Create
DNSProviderCredentialmodel inbackend/internal/models/dns_provider_credential.gowith gorm tags - Add
UseMultiCredentialsfield toDNSProvidermodel - Run
db.AutoMigrate(&models.DNSProviderCredential{}, &models.DNSProvider{})in the application initialization code - GORM will automatically create the table, foreign keys, and indexes
Note: The key_version field tracks which encryption key version was used. Versions are managed via environment variables only (see Feature 2).
DNSProviderCredential Model
// DNSProviderCredential represents a zone-specific credential set.
type DNSProviderCredential struct {
ID uint `json:"id" gorm:"primaryKey"`
UUID string `json:"uuid" gorm:"uniqueIndex;size:36"`
DNSProviderID uint `json:"dns_provider_id" gorm:"index;not null"`
DNSProvider *DNSProvider `json:"dns_provider,omitempty" gorm:"foreignKey:DNSProviderID"`
Label string `json:"label" gorm:"not null;size:255"`
ZoneFilter string `json:"zone_filter" gorm:"type:text"` // Comma-separated domains
CredentialsEncrypted string `json:"-" gorm:"type:text;not null"`
Enabled bool `json:"enabled" gorm:"default:true"`
PropagationTimeout int `json:"propagation_timeout" gorm:"default:120"`
PollingInterval int `json:"polling_interval" gorm:"default:5"`
KeyVersion int `json:"key_version" gorm:"default:1"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
SuccessCount int `json:"success_count" gorm:"default:0"`
FailureCount int `json:"failure_count" gorm:"default:0"`
LastError string `json:"last_error,omitempty" gorm:"type:text"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Zone Matching Algorithm
// GetCredentialForDomain selects the best credential match for a domain
// Priority: exact match > wildcard match > catch-all (empty zone_filter)
func (s *dnsProviderService) GetCredentialForDomain(ctx context.Context, providerID uint, domain string) (*models.DNSProviderCredential, error) {
// 1. If not using multi-credentials, return default
// 2. Find exact domain match
// 3. Find wildcard match (*.example.com matches app.example.com)
// 4. Find catch-all (empty zone_filter)
// 5. Return error if no match
}
API Endpoints
| Method | Endpoint | Handler | Description |
|---|---|---|---|
GET |
/api/v1/dns-providers/:id/credentials |
CredentialHandler.List |
List all credentials |
POST |
/api/v1/dns-providers/:id/credentials |
CredentialHandler.Create |
Create credential |
GET |
/api/v1/dns-providers/:id/credentials/:cred_id |
CredentialHandler.Get |
Get single credential |
PUT |
/api/v1/dns-providers/:id/credentials/:cred_id |
CredentialHandler.Update |
Update credential |
DELETE |
/api/v1/dns-providers/:id/credentials/:cred_id |
CredentialHandler.Delete |
Delete credential |
POST |
/api/v1/dns-providers/:id/credentials/:cred_id/test |
CredentialHandler.Test |
Test credential |
POST |
/api/v1/dns-providers/:id/enable-multi-credentials |
CredentialHandler.EnableMulti |
Migrate to multi-credential mode |
Definition of Done
- DNSProviderCredential model created
- Database migration runs without errors
- CRUD operations for credentials work
- Zone matching algorithm implemented and tested
- Credential selection integrated with Caddy config generation
- Migration from single to multi-credential mode works
- Backward compatibility maintained (providers default to single credential)
- Frontend CredentialManager modal functional
- Audit events logged for credential operations
- Backend tests achieve ≥85% coverage
- Frontend tests achieve ≥85% coverage
- Documentation updated with multi-credential setup guide
Feature 4: DNS Provider Auto-Detection
Overview
- Priority: P2
- Estimated Time: 6-8 hours
- Dependencies: None (can be developed in parallel)
File Inventory
Backend Files to Create
| File | Purpose |
|---|---|
backend/internal/services/dns_detection_service.go |
Nameserver lookup and pattern matching |
backend/internal/services/dns_detection_service_test.go |
Unit tests (85% coverage target) |
backend/internal/api/handlers/dns_detection_handler.go |
REST API for detection |
backend/internal/api/handlers/dns_detection_handler_test.go |
Unit tests |
Backend Files to Modify
| File | Changes |
|---|---|
| backend/internal/api/routes/routes.go | Register detection route |
Frontend Files to Create
| File | Purpose |
|---|---|
frontend/src/api/dnsDetection.ts |
API client for detection |
frontend/src/hooks/useDNSDetection.ts |
React Query hooks |
Frontend Files to Modify
| File | Changes |
|---|---|
frontend/src/pages/ProxyHosts.tsx |
Integrate auto-detection in form |
Nameserver Pattern Database
// BuiltInNameservers maps nameserver patterns to provider types
var BuiltInNameservers = map[string]string{
// Cloudflare
".ns.cloudflare.com": "cloudflare",
// AWS Route 53
".awsdns": "route53",
// DigitalOcean
".digitalocean.com": "digitalocean",
// Google Cloud DNS
".googledomains.com": "googleclouddns",
"ns-cloud": "googleclouddns",
// Azure DNS
".azure-dns": "azure",
// Namecheap
".registrar-servers.com": "namecheap",
// GoDaddy
".domaincontrol.com": "godaddy",
// Hetzner
".hetzner.com": "hetzner",
".hetzner.de": "hetzner",
// Vultr
".vultr.com": "vultr",
// DNSimple
".dnsimple.com": "dnsimple",
}
DNSDetectionService Interface
type DNSDetectionService interface {
// DetectProvider identifies the DNS provider for a domain
DetectProvider(domain string) (*DetectionResult, error)
// SuggestConfiguredProvider finds a matching configured provider
SuggestConfiguredProvider(ctx context.Context, domain string) (*models.DNSProvider, error)
// GetNameserverPatterns returns current pattern database
GetNameserverPatterns() map[string]string
}
type DetectionResult struct {
Domain string `json:"domain"`
Detected bool `json:"detected"`
ProviderType string `json:"provider_type,omitempty"`
Nameservers []string `json:"nameservers"`
Confidence string `json:"confidence"` // "high", "medium", "low", "none"
SuggestedProvider *models.DNSProvider `json:"suggested_provider,omitempty"`
Error string `json:"error,omitempty"`
}
API Endpoint
| Method | Endpoint | Handler | Description |
|---|---|---|---|
POST |
/api/v1/dns-providers/detect |
DNSDetectionHandler.Detect |
Detect provider for domain |
Request:
{
"domain": "example.com"
}
Response:
{
"domain": "example.com",
"detected": true,
"provider_type": "cloudflare",
"nameservers": ["ns1.cloudflare.com", "ns2.cloudflare.com"],
"confidence": "high",
"suggested_provider": {
"id": 1,
"name": "Production Cloudflare",
"provider_type": "cloudflare"
}
}
Frontend Integration
// In ProxyHostForm.tsx
const { detectProvider, isDetecting } = useDNSDetection()
useEffect(() => {
if (hasWildcardDomain && domain) {
const baseDomain = domain.replace(/^\*\./, '')
detectProvider(baseDomain).then(result => {
if (result.suggested_provider) {
setDNSProviderID(result.suggested_provider.id)
toast.info(`Auto-detected: ${result.suggested_provider.name}`)
}
})
}
}, [domain, hasWildcardDomain])
Definition of Done
- DNSDetectionService created with pattern matching
- Nameserver lookup via
net.LookupNS()works - Results cached with 1-hour TTL
- Detection endpoint returns provider suggestions
- Frontend auto-fills DNS provider on wildcard domain entry
- Manual override available (user can change detected provider)
- Detection accuracy >95% for supported providers
- Backend tests achieve ≥85% coverage
- Frontend tests achieve ≥85% coverage
- Performance: detection <500ms per domain
Feature 5: Custom DNS Provider Plugins
Overview
- Priority: P3
- Estimated Time: 20-24 hours
- Dependencies: All above features (mature and stable before adding plugins)
⚠️ Platform Limitation: Go plugins (.so files) are only supported on Linux and macOS. Windows does not support Go's plugin system. For Windows deployments, use Docker containers (recommended) or compile all providers as built-in.
File Inventory
Backend Files to Create
| File | Purpose |
|---|---|
backend/pkg/dnsprovider/interface.go |
Plugin interface definition |
backend/pkg/dnsprovider/builtin.go |
Built-in providers adapter |
backend/internal/services/plugin_loader.go |
Plugin loading and management |
backend/internal/services/plugin_loader_test.go |
Unit tests |
backend/internal/api/handlers/plugin_handler.go |
REST API for plugin management |
backend/internal/api/handlers/plugin_handler_test.go |
Unit tests |
Example Plugin (separate directory)
| File | Purpose |
|---|---|
plugins/powerdns/powerdns_plugin.go |
Example PowerDNS plugin |
plugins/powerdns/README.md |
Plugin documentation |
Documentation
| File | Purpose |
|---|---|
docs/development/dns-plugins.md |
Plugin development guide |
docs/development/plugin-security.md |
Security guidelines for plugins |
Frontend Files to Create
| File | Purpose |
|---|---|
frontend/src/api/plugins.ts |
API client for plugins |
frontend/src/hooks/usePlugins.ts |
React Query hooks |
frontend/src/pages/PluginManagement.tsx |
Admin page for plugin management |
frontend/src/pages/__tests__/PluginManagement.test.tsx |
Component tests |
Plugin Interface
// Package dnsprovider defines the interface for DNS provider plugins.
package dnsprovider
import "time"
// Provider is the interface that DNS provider plugins must implement.
type Provider interface {
// GetType returns the provider type identifier (e.g., "custom_powerdns")
GetType() string
// GetMetadata returns provider metadata for UI
GetMetadata() ProviderMetadata
// ValidateCredentials checks if credentials are valid
ValidateCredentials(credentials map[string]string) error
// CreateTXTRecord creates a DNS TXT record for ACME challenge
CreateTXTRecord(zone, name, value string, credentials map[string]string) error
// DeleteTXTRecord removes a DNS TXT record after challenge
DeleteTXTRecord(zone, name string, credentials map[string]string) error
// GetPropagationTimeout returns recommended DNS propagation wait time
GetPropagationTimeout() time.Duration
}
// ProviderMetadata describes a DNS provider plugin.
type ProviderMetadata struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
DocumentationURL string `json:"documentation_url"`
CredentialFields []CredentialField `json:"credential_fields"`
Author string `json:"author"`
Version string `json:"version"`
}
// CredentialField describes a credential input field.
type CredentialField struct {
Name string `json:"name"`
Label string `json:"label"`
Type string `json:"type"` // "text", "password", "textarea"
Required bool `json:"required"`
Placeholder string `json:"placeholder,omitempty"`
Hint string `json:"hint,omitempty"`
}
Plugin Loader Service
type PluginLoader interface {
// LoadPlugins scans plugin directory and loads all valid plugins
LoadPlugins() error
// LoadPlugin loads a single plugin from path
LoadPlugin(path string) error
// GetProvider returns a loaded plugin provider
GetProvider(providerType string) (dnsprovider.Provider, bool)
// ListProviders returns metadata for all loaded plugins
ListProviders() []dnsprovider.ProviderMetadata
// VerifySignature validates plugin signature
VerifySignature(pluginPath string, expectedSig string) error
}
Security Requirements
- Signature Verification: All plugins must have SHA-256 hash verified
- Allowlist: Admin must explicitly enable each plugin
- Configuration file:
config/plugins.yaml
dns_providers:
- plugin: powerdns
enabled: true
verified_signature: "sha256:abcd1234..."
- plugin: custom_internal
enabled: false
API Endpoints
| Method | Endpoint | Handler | Description |
|---|---|---|---|
GET |
/api/v1/admin/plugins |
PluginHandler.List |
List loaded plugins |
GET |
/api/v1/admin/plugins/:type |
PluginHandler.Get |
Get plugin details |
POST |
/api/v1/admin/plugins/:type/enable |
PluginHandler.Enable |
Enable plugin |
POST |
/api/v1/admin/plugins/:type/disable |
PluginHandler.Disable |
Disable plugin |
POST |
/api/v1/admin/plugins/reload |
PluginHandler.Reload |
Reload all plugins |
Definition of Done
- Plugin interface defined in
backend/pkg/dnsprovider/ - PluginLoader service loads
.sofiles fromCHARON_PLUGIN_DIR(Linux/macOS only) - Signature verification implemented
- Allowlist enforcement works
- Example PowerDNS plugin compiles and loads on Linux/macOS
- Built-in providers wrapped to use same interface
- Admin UI shows loaded plugins
- Plugin development guide written with platform limitations documented
- Docker deployment guide emphasizes Docker as primary target for plugins
- Windows compatibility note added (plugins not supported, use Docker)
- Backend tests achieve ≥85% coverage
- Frontend tests achieve ≥85% coverage
- Security review completed
Code Sharing and Pattern Reuse
Shared Components
Several features can share code to reduce duplication:
| Shared Pattern | Used By | Location |
|---|---|---|
| Pagination helper | Audit Logs, Credentials | backend/internal/api/handlers/pagination.go |
| Encryption/Decryption | DNS Service, Credential Service, Rotation Service | backend/internal/crypto/ |
| Audit logging helper | All CRUD operations | backend/internal/services/audit_helper.go |
| SecurityService extension | Audit log filtering/pagination | backend/internal/services/security_service.go (extend existing) |
| Async audit channel | Non-blocking audit logging | Buffered channel (capacity: 100) in SecurityService |
| React Query key factory | All new hooks | frontend/src/hooks/queryKeys.ts |
| Table with filters component | Audit Logs, Credentials | frontend/src/components/DataTable.tsx |
Audit Logging Helper
Create a reusable helper with async buffered channel for non-blocking audit logging:
// backend/internal/services/audit_helper.go
type AuditHelper struct {
securityService *SecurityService
auditChan chan *models.SecurityAudit // Buffered channel (capacity: 100)
}
func NewAuditHelper(securityService *SecurityService) *AuditHelper {
h := &AuditHelper{
securityService: securityService,
auditChan: make(chan *models.SecurityAudit, 100),
}
go h.processAuditEvents() // Background goroutine
return h
}
func (h *AuditHelper) LogDNSProviderEvent(ctx context.Context, action string, provider *models.DNSProvider, details map[string]interface{}) {
detailsJSON, _ := json.Marshal(details)
audit := &models.SecurityAudit{
Actor: getUserIDFromContext(ctx),
Action: action,
EventCategory: "dns_provider",
ResourceID: &provider.ID,
ResourceUUID: provider.UUID,
Details: string(detailsJSON),
IPAddress: getIPFromContext(ctx),
UserAgent: getUserAgentFromContext(ctx),
}
// Non-blocking send (drops if buffer full to avoid blocking main operations)
select {
case h.auditChan <- audit:
default:
// Log dropped event metric
}
}
func (h *AuditHelper) processAuditEvents() {
for audit := range h.auditChan {
h.securityService.LogAudit(audit)
}
}
Frontend Query Key Factory
// frontend/src/hooks/queryKeys.ts
export const queryKeys = {
// DNS Providers
dnsProviders: {
all: ['dns-providers'] as const,
lists: () => [...queryKeys.dnsProviders.all, 'list'] as const,
detail: (id: number) => [...queryKeys.dnsProviders.all, 'detail', id] as const,
credentials: (id: number) => [...queryKeys.dnsProviders.all, id, 'credentials'] as const,
},
// Audit Logs
auditLogs: {
all: ['audit-logs'] as const,
list: (filters?: AuditLogFilters) => [...queryKeys.auditLogs.all, 'list', filters] as const,
detail: (uuid: string) => [...queryKeys.auditLogs.all, 'detail', uuid] as const,
byProvider: (id: number) => [...queryKeys.auditLogs.all, 'provider', id] as const,
},
// Encryption
encryption: {
all: ['encryption'] as const,
status: () => [...queryKeys.encryption.all, 'status'] as const,
history: () => [...queryKeys.encryption.all, 'history'] as const,
},
// Detection
detection: {
all: ['detection'] as const,
result: (domain: string) => [...queryKeys.detection.all, domain] as const,
},
// Plugins
plugins: {
all: ['plugins'] as const,
list: () => [...queryKeys.plugins.all, 'list'] as const,
detail: (type: string) => [...queryKeys.plugins.all, 'detail', type] as const,
},
}
Risk Assessment
Risk Matrix
| Feature | Risk Level | Main Risks | Mitigation |
|---|---|---|---|
| Audit Logging | Low | Log volume growth | Retention policy, indexed queries, async logging |
| Key Rotation | Medium | Data loss, downtime | Backup before rotation, rollback procedure, staged rollout |
| Multi-Credential | Medium | Zone mismatch, credential isolation | Thorough zone matching tests, fallback to catch-all |
| Auto-Detection | Low | False positives | Manual override, confidence scoring |
| Custom Plugins | High | Code execution, security | Signature verification, allowlist, security review |
Breaking Changes Assessment
| Feature | Breaking Changes | Backward Compatibility |
|---|---|---|
| Audit Logging | None | Full - additive only |
| Key Rotation | None | Full - transparent to API consumers |
| Multi-Credential | None | Full - use_multi_credentials defaults to false |
| Auto-Detection | None | Full - opt-in feature |
| Custom Plugins | None | Full - built-in providers continue working |
Rollback Procedures
- Audit Logging: Drop new columns from
security_auditstable - Key Rotation: Keep old
CHARON_ENCRYPTION_KEYconfigured, remove_NEXTand_V*vars - Multi-Credential: Set
use_multi_credentials=falsefor all providers - Auto-Detection: Disable in frontend, remove detection routes
- Custom Plugins: Disable all plugins via allowlist, remove plugin directory
Testing Strategy
Coverage Requirements
All new code must achieve ≥85% test coverage per project requirements.
Test Categories
| Category | Tools | Focus Areas |
|---|---|---|
| Unit Tests (Backend) | go test |
Services, handlers, crypto |
| Unit Tests (Frontend) | Jest, React Testing Library | Hooks, components |
| Integration Tests | go test -tags=integration |
Database operations, API endpoints |
| E2E Tests | Playwright | Full user flows |
Test Files Required
| Feature | Backend Tests | Frontend Tests |
|---|---|---|
| Audit Logging | audit_log_handler_test.go, security_service_test.go (extend) |
AuditLogs.test.tsx |
| Key Rotation | rotation_service_test.go, encryption_handler_test.go |
EncryptionManagement.test.tsx |
| Multi-Credential | credential_service_test.go, credential_handler_test.go |
CredentialManager.test.tsx |
| Auto-Detection | dns_detection_service_test.go, dns_detection_handler_test.go |
useDNSDetection.test.ts |
| Custom Plugins | plugin_loader_test.go, plugin_handler_test.go |
PluginManagement.test.tsx |
Test Commands
# Backend tests
cd backend && go test ./... -cover -coverprofile=coverage.out
# Check coverage percentage
go tool cover -func=coverage.out | grep total
# Frontend tests
cd frontend && npm test -- --coverage
# Run specific test file
cd backend && go test -v ./internal/services/audit_log_service_test.go
Appendix: Full File Tree
backend/
├── internal/
│ ├── api/
│ │ ├── handlers/
│ │ │ ├── audit_log_handler.go # NEW (Feature 1)
│ │ │ ├── audit_log_handler_test.go # NEW (Feature 1)
│ │ │ ├── credential_handler.go # NEW (Feature 3)
│ │ │ ├── credential_handler_test.go # NEW (Feature 3)
│ │ │ ├── dns_detection_handler.go # NEW (Feature 4)
│ │ │ ├── dns_detection_handler_test.go # NEW (Feature 4)
│ │ │ ├── dns_provider_handler.go # EXISTING
│ │ │ ├── encryption_handler.go # NEW (Feature 2)
│ │ │ ├── encryption_handler_test.go # NEW (Feature 2)
│ │ │ └── plugin_handler.go # NEW (Feature 5)
│ │ └── routes/
│ │ └── routes.go # MODIFY
│ ├── crypto/
│ │ ├── encryption.go # MODIFY (Feature 2)
│ │ ├── encryption_test.go # EXISTING
│ │ ├── rotation_service.go # NEW (Feature 2)
│ │ └── rotation_service_test.go # NEW (Feature 2)
│ ├── models/
│ │ ├── dns_provider.go # MODIFY (Features 2, 3)
│ │ ├── dns_provider_credential.go # NEW (Feature 3)
│ │ └── security_audit.go # MODIFY (Feature 1)
│ ├── services/
│ │ ├── audit_helper.go # NEW (shared, async buffered channel)
│ │ ├── credential_service.go # NEW (Feature 3)
│ │ ├── credential_service_test.go # NEW (Feature 3)
│ │ ├── dns_detection_service.go # NEW (Feature 4)
│ │ ├── dns_detection_service_test.go # NEW (Feature 4)
│ │ ├── dns_provider_service.go # MODIFY (Features 1, 2, 3)
│ │ ├── plugin_loader.go # NEW (Feature 5)
│ │ ├── plugin_loader_test.go # NEW (Feature 5)
│ │ └── security_service.go # EXTEND (Feature 1: add ListAuditLogs)
├── pkg/
│ └── dnsprovider/
│ ├── interface.go # NEW (Feature 5)
│ └── builtin.go # NEW (Feature 5)
└── plugins/
└── powerdns/
├── powerdns_plugin.go # NEW (Feature 5)
└── README.md # NEW (Feature 5)
frontend/
├── src/
│ ├── api/
│ │ ├── auditLogs.ts # NEW (Feature 1)
│ │ ├── credentials.ts # NEW (Feature 3)
│ │ ├── dnsDetection.ts # NEW (Feature 4)
│ │ ├── dnsProviders.ts # EXISTING
│ │ ├── encryption.ts # NEW (Feature 2)
│ │ └── plugins.ts # NEW (Feature 5)
│ ├── components/
│ │ ├── CredentialManager.tsx # NEW (Feature 3)
│ │ ├── DataTable.tsx # NEW (shared)
│ │ └── __tests__/
│ │ └── CredentialManager.test.tsx # NEW (Feature 3)
│ ├── hooks/
│ │ ├── queryKeys.ts # NEW (shared)
│ │ ├── useAuditLogs.ts # NEW (Feature 1)
│ │ ├── useCredentials.ts # NEW (Feature 3)
│ │ ├── useDNSDetection.ts # NEW (Feature 4)
│ │ ├── useDNSProviders.ts # EXISTING
│ │ ├── useEncryption.ts # NEW (Feature 2)
│ │ └── usePlugins.ts # NEW (Feature 5)
│ └── pages/
│ ├── AuditLogs.tsx # NEW (Feature 1)
│ ├── DNSProviders.tsx # MODIFY (Feature 3)
│ ├── EncryptionManagement.tsx # NEW (Feature 2)
│ ├── PluginManagement.tsx # NEW (Feature 5)
│ ├── ProxyHosts.tsx # MODIFY (Feature 4)
│ └── __tests__/
│ ├── AuditLogs.test.tsx # NEW (Feature 1)
│ ├── EncryptionManagement.test.tsx # NEW (Feature 2)
│ └── PluginManagement.test.tsx # NEW (Feature 5)
docs/
├── development/
│ ├── dns-plugins.md # NEW (Feature 5)
│ └── plugin-security.md # NEW (Feature 5)
└── plans/
├── dns_challenge_future_features.md # EXISTING (source spec)
└── dns_future_features_implementation.md # THIS FILE
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0.0 | January 3, 2026 | Planning Agent | Initial comprehensive implementation plan |
| 1.1.0 | January 3, 2026 | Planning Agent | Applied supervisor corrections: GORM AutoMigrate pattern, removed encryption_keys table, added Linux/macOS plugin limitation, consolidated AuditLogService into SecurityService, added router integration, specified buffered channel strategy |
Status: Ready for implementation. Begin with Phase 1 (Audit Logging).