Files
Charon/docs/plans/dns_future_features_implementation.md
GitHub Actions 111a8cc1dc feat: implement encryption management features including key rotation, validation, and history tracking
- 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.
2026-01-04 03:08:40 +00:00

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

  1. Executive Summary
  2. Phase Breakdown
  3. Feature 1: Audit Logging for Credential Operations
  4. Feature 2: Key Rotation Automation
  5. Feature 3: Multi-Credential per Provider
  6. Feature 4: DNS Provider Auto-Detection
  7. Feature 5: Custom DNS Provider Plugins
  8. Code Sharing and Pattern Reuse
  9. Risk Assessment
  10. 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.

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:


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)

  1. Update the SecurityAudit model in backend/internal/models/security_audit.go with new fields and indexes
  2. Run db.AutoMigrate(&models.SecurityAudit{}) in the application initialization code
  3. 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)

  1. Add KeyVersion field to DNSProvider model with gorm index tag
  2. Run db.AutoMigrate(&models.DNSProvider{}) in the application initialization code
  3. GORM will automatically add the key_version column 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:

  1. Set CHARON_ENCRYPTION_KEY_V2 with new key
  2. Restart application (loads both keys)
  3. Trigger /api/v1/admin/encryption/rotate endpoint
  4. All credentials re-encrypted with V2
  5. Rename: CHARON_ENCRYPTION_KEY_V2CHARON_ENCRYPTION_KEY, old key → CHARON_ENCRYPTION_KEY_V1
  6. 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)

  1. Create DNSProviderCredential model in backend/internal/models/dns_provider_credential.go with gorm tags
  2. Add UseMultiCredentials field to DNSProvider model
  3. Run db.AutoMigrate(&models.DNSProviderCredential{}, &models.DNSProvider{}) in the application initialization code
  4. 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

  1. Signature Verification: All plugins must have SHA-256 hash verified
  2. Allowlist: Admin must explicitly enable each plugin
  3. 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 .so files from CHARON_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

  1. Audit Logging: Drop new columns from security_audits table
  2. Key Rotation: Keep old CHARON_ENCRYPTION_KEY configured, remove _NEXT and _V* vars
  3. Multi-Credential: Set use_multi_credentials=false for all providers
  4. Auto-Detection: Disable in frontend, remove detection routes
  5. 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).