Files
Charon/docs/implementation/dns_providers_IMPLEMENTATION.md
akanealw eec8c28fb3
Some checks are pending
Go Benchmark / Performance Regression Check (push) Waiting to run
Cerberus Integration / Cerberus Security Stack Integration (push) Waiting to run
Upload Coverage to Codecov / Backend Codecov Upload (push) Waiting to run
Upload Coverage to Codecov / Frontend Codecov Upload (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (go) (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (javascript-typescript) (push) Waiting to run
CrowdSec Integration / CrowdSec Bouncer Integration (push) Waiting to run
Docker Build, Publish & Test / build-and-push (push) Waiting to run
Docker Build, Publish & Test / Security Scan PR Image (push) Blocked by required conditions
Quality Checks / Auth Route Protection Contract (push) Waiting to run
Quality Checks / Codecov Trigger/Comment Parity Guard (push) Waiting to run
Quality Checks / Backend (Go) (push) Waiting to run
Quality Checks / Frontend (React) (push) Waiting to run
Rate Limit integration / Rate Limiting Integration (push) Waiting to run
Security Scan (PR) / Trivy Binary Scan (push) Waiting to run
Supply Chain Verification (PR) / Verify Supply Chain (push) Waiting to run
WAF integration / Coraza WAF Integration (push) Waiting to run
changed perms
2026-04-22 18:19:14 +00:00

32 KiB
Executable File
Raw Permalink Blame History

DNS Providers — Implementation Spec

This document was relocated from the former multi-topic docs/plans/current_spec.md to keep the current plan index SSRF-only.


2. Scope & Acceptance Criteria

In Scope

  • DNSProvider model with encrypted credential storage
  • API endpoints for DNS provider CRUD operations
  • Provider connectivity testing (pre-save and post-save)
  • Caddy DNS challenge configuration generation
  • Frontend management UI for DNS providers
  • Integration with proxy host creation (wildcard detection)
  • Support for major DNS providers: Cloudflare, Route53, DigitalOcean, Google Cloud DNS, Namecheap, GoDaddy, Azure DNS, Hetzner, Vultr, DNSimple

Out of Scope (Future Iterations)

  • Multi-credential per provider (zone-specific credentials)
  • Key rotation automation
  • DNS provider auto-detection
  • Custom DNS provider plugins

Acceptance Criteria

  • Users can add, edit, delete, and test DNS provider configurations
  • Credentials are encrypted at rest using AES-256-GCM
  • Credentials are never exposed in API responses (masked or omitted)
  • Proxy hosts with wildcard domains can select a DNS provider
  • Caddy successfully obtains wildcard certificates using DNS-01 challenge
  • Backend unit test coverage ≥ 85%
  • Frontend unit test coverage ≥ 85%
  • User documentation completed
  • All translations added for new UI strings

3. Technical Architecture

Component Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                               FRONTEND                                       │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────┐  │
│  │ DNSProviders    │  │ DNSProviderForm │  │ ProxyHostForm               │  │
│  │ Page            │  │ (Add/Edit)      │  │ (Wildcard + Provider Select)│  │
│  └────────┬────────┘  └────────┬────────┘  └─────────────┬───────────────┘  │
│           │                    │                         │                   │
│           └────────────────────┼─────────────────────────┘                   │
│                                ▼                                             │
│                    ┌───────────────────────┐                                 │
│                    │ api/dnsProviders.ts   │                                 │
│                    │ hooks/useDNSProviders │                                 │
│                    └───────────┬───────────┘                                 │
└────────────────────────────────┼─────────────────────────────────────────────┘
                                 │ HTTP/JSON
                                 ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                               BACKEND                                        │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                        API Layer (Gin Router)                          │ │
│  │  /api/v1/dns-providers/*  →  dns_provider_handler.go                   │ │
│  └────────────────────────────────┬───────────────────────────────────────┘ │
│                                   ▼                                          │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                      Service Layer                                      │
│  │  dns_provider_service.go  ←→  crypto/encryption.go (AES-256-GCM)       │
│  └────────────────────────────────┬───────────────────────────────────────┘ │
│                                   ▼                                          │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                        Data Layer (GORM)                               │
│  │  models/dns_provider.go  │  models/proxy_host.go (extended)            │
│  └────────────────────────────────┬───────────────────────────────────────┘ │
│                                   ▼                                          │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                     Caddy Integration                                   │
│  │  caddy/config.go  →  DNS Challenge Issuer Config  →  Caddy Admin API   │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                           DNS PROVIDER                                       │
│                    (Cloudflare, Route53, etc.)                               │
│                    TXT Record: _acme-challenge.example.com                   │
└─────────────────────────────────────────────────────────────────────────────┘

Data Flow for DNS Challenge

1. User creates ProxyHost with *.example.com + selects DNSProvider
                              │
                              ▼
2. Backend validates request, fetches DNSProvider credentials (decrypted)
                              │
                              ▼
3. Caddy Manager generates config with DNS challenge issuer:
   {
     "module": "acme",
     "challenges": {
       "dns": {
         "provider": { "name": "cloudflare", "api_token": "..." }
       }
     }
   }
                              │
                              ▼
4. Caddy applies config → initiates ACME order → requests DNS challenge
                              │
                              ▼
5. Caddy's DNS provider module creates TXT record via DNS API
                              │
                              ▼
6. ACME server validates TXT record → issues certificate
                              │
                              ▼
7. Caddy stores certificate → serves HTTPS for *.example.com

4. Database Schema

DNSProvider Model

// File: backend/internal/models/dns_provider.go

// DNSProvider represents a DNS provider configuration for ACME DNS-01 challenges.
type DNSProvider struct {
    ID                   uint       `json:"id" gorm:"primaryKey"`
    UUID                 string     `json:"uuid" gorm:"uniqueIndex;size:36"`
    Name                 string     `json:"name" gorm:"index;not null;size:255"`
    ProviderType         string     `json:"provider_type" gorm:"index;not null;size:50"`
    Enabled              bool       `json:"enabled" gorm:"default:true;index"`
    IsDefault            bool       `json:"is_default" gorm:"default:false"`

    // Encrypted credentials (JSON blob, encrypted with AES-256-GCM)
    CredentialsEncrypted string     `json:"-" gorm:"type:text;column:credentials_encrypted"`

    // Propagation settings
    PropagationTimeout   int        `json:"propagation_timeout" gorm:"default:120"`  // seconds
    PollingInterval      int        `json:"polling_interval" gorm:"default:5"`       // seconds

    // Usage tracking
    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"`
}

// TableName specifies the database table name
func (DNSProvider) TableName() string {
    return "dns_providers"
}

ProxyHost Extensions

// File: backend/internal/models/proxy_host.go (additions)

type ProxyHost struct {
    // ... existing fields ...

    // DNS Challenge configuration
    DNSProviderID   *uint        `json:"dns_provider_id,omitempty" gorm:"index"`
    DNSProvider     *DNSProvider `json:"dns_provider,omitempty" gorm:"foreignKey:DNSProviderID"`
    UseDNSChallenge bool         `json:"use_dns_challenge" gorm:"default:false"`
}

Supported Provider Types

Provider Type Credential Fields Caddy DNS Module
cloudflare api_token OR (api_key, email) cloudflare
route53 access_key_id, secret_access_key, region route53
digitalocean auth_token digitalocean
googleclouddns service_account_json, project googleclouddns
namecheap api_user, api_key, client_ip namecheap
godaddy api_key, api_secret godaddy
azure tenant_id, client_id, client_secret, subscription_id, resource_group azuredns
hetzner api_key hetzner
vultr api_key vultr
dnsimple oauth_token, account_id dnsimple

5. API Specification

Endpoints

Method Endpoint Description
GET /api/v1/dns-providers List all DNS providers
POST /api/v1/dns-providers Create new DNS provider
GET /api/v1/dns-providers/:id Get provider details
PUT /api/v1/dns-providers/:id Update provider
DELETE /api/v1/dns-providers/:id Delete provider
POST /api/v1/dns-providers/:id/test Test saved provider
POST /api/v1/dns-providers/test Test credentials (pre-save)
GET /api/v1/dns-providers/types List supported provider types

Request/Response Schemas

Create DNS Provider

Request: POST /api/v1/dns-providers

{
  "name": "Production Cloudflare",
  "provider_type": "cloudflare",
  "credentials": {
    "api_token": "xxxxxxxxxxxxxxxxxxxxxxxxxx"
  },
  "propagation_timeout": 120,
  "polling_interval": 5,
  "is_default": true
}

Response: 201 Created

{
  "id": 1,
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Production Cloudflare",
  "provider_type": "cloudflare",
  "enabled": true,
  "is_default": true,
  "has_credentials": true,
  "propagation_timeout": 120,
  "polling_interval": 5,
  "success_count": 0,
  "failure_count": 0,
  "created_at": "2026-01-01T12:00:00Z",
  "updated_at": "2026-01-01T12:00:00Z"
}

List DNS Providers

Response: GET /api/v1/dns-providers200 OK

{
  "providers": [
    {
      "id": 1,
      "uuid": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Production Cloudflare",
      "provider_type": "cloudflare",
      "enabled": true,
      "is_default": true,
      "has_credentials": true,
      "propagation_timeout": 120,
      "polling_interval": 5,
      "last_used_at": "2026-01-01T10:30:00Z",
      "success_count": 15,
      "failure_count": 0,
      "created_at": "2025-12-01T08:00:00Z",
      "updated_at": "2026-01-01T10:30:00Z"
    }
  ],
  "total": 1
}

Test DNS Provider

Request: POST /api/v1/dns-providers/:id/test

Response: 200 OK

{
  "success": true,
  "message": "DNS provider credentials validated successfully",
  "propagation_time_ms": 2340
}

Error Response: 400 Bad Request

{
  "success": false,
  "error": "Authentication failed: invalid API token",
  "code": "INVALID_CREDENTIALS"
}

Get Provider Types

Response: GET /api/v1/dns-providers/types200 OK

{
  "types": [
    {
      "type": "cloudflare",
      "name": "Cloudflare",
      "fields": [
        { "name": "api_token", "label": "API Token", "type": "password", "required": true, "hint": "Token with Zone:DNS:Edit permissions" }
      ],
      "documentation_url": "https://developers.cloudflare.com/api/tokens/"
    },
    {
      "type": "route53",
      "name": "Amazon Route 53",
      "fields": [
        { "name": "access_key_id", "label": "Access Key ID", "type": "text", "required": true },
        { "name": "secret_access_key", "label": "Secret Access Key", "type": "password", "required": true },
        { "name": "region", "label": "AWS Region", "type": "text", "required": true, "default": "us-east-1" }
      ],
      "documentation_url": "https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-routing-traffic.html"
    }
  ]
}

6. Backend Implementation

Phase 1: Encryption Package + DNSProvider Model (~2-3 hours)

Objective: Create secure credential storage foundation

Files to Create

File Description Complexity
backend/internal/crypto/encryption.go AES-256-GCM encryption service Medium
backend/internal/crypto/encryption_test.go Encryption unit tests Low
backend/internal/models/dns_provider.go DNSProvider model + validation Medium

Implementation Details

Encryption Service:

// backend/internal/crypto/encryption.go
package crypto

type EncryptionService struct {
    key []byte // 32 bytes for AES-256
}

func NewEncryptionService(keyBase64 string) (*EncryptionService, error)
func (s *EncryptionService) Encrypt(plaintext []byte) (string, error)
func (s *EncryptionService) Decrypt(ciphertextB64 string) ([]byte, error)

Configuration Extension:

// backend/internal/config/config.go (add)
EncryptionKey string `env:"CHARON_ENCRYPTION_KEY"`

Phase 2: Service Layer + Handlers (~2-3 hours)

Objective: Build DNS provider CRUD operations

Files to Create

File Description Complexity
backend/internal/services/dns_provider_service.go DNS provider CRUD + crypto integration High
backend/internal/services/dns_provider_service_test.go Service unit tests Medium
backend/internal/api/handlers/dns_provider_handler.go HTTP handlers Medium
backend/internal/api/handlers/dns_provider_handler_test.go Handler unit tests Medium

Service Interface

type DNSProviderService interface {
    List(ctx context.Context) ([]DNSProvider, error)
    Get(ctx context.Context, id uint) (*DNSProvider, error)
    Create(ctx context.Context, req CreateDNSProviderRequest) (*DNSProvider, error)
    Update(ctx context.Context, id uint, req UpdateDNSProviderRequest) (*DNSProvider, error)
    Delete(ctx context.Context, id uint) error
    Test(ctx context.Context, id uint) (*TestResult, error)
    TestCredentials(ctx context.Context, req CreateDNSProviderRequest) (*TestResult, error)
    GetDecryptedCredentials(ctx context.Context, id uint) (map[string]string, error)
}

Phase 3: Caddy Integration (~2 hours)

Objective: Generate DNS challenge configuration for Caddy

Files to Modify

File Changes Complexity
backend/internal/caddy/types.go Add DNSChallengeConfig, ChallengesConfig types Low
backend/internal/caddy/config.go Add DNS challenge issuer generation logic High
backend/internal/caddy/manager.go Fetch DNS providers when applying config Medium
backend/internal/api/routes/routes.go Register DNS provider routes Low

Caddy Types Addition

// backend/internal/caddy/types.go

type DNSChallengeConfig struct {
    Provider           map[string]any `json:"provider"`
    PropagationTimeout int64          `json:"propagation_timeout,omitempty"` // nanoseconds
    Resolvers          []string       `json:"resolvers,omitempty"`
}

type ChallengesConfig struct {
    DNS *DNSChallengeConfig `json:"dns,omitempty"`
}

7. Frontend Implementation

Phase 1: API Client + Hooks (~1-2 hours)

Objective: Establish data layer for DNS providers

Files to Create

File Description Complexity
frontend/src/api/dnsProviders.ts API client functions Low
frontend/src/hooks/useDNSProviders.ts React Query hooks Low
frontend/src/data/dnsProviderSchemas.ts Provider field definitions Low

Phase 2: DNS Providers Page (~2-3 hours)

Objective: Complete management UI for DNS providers

Files to Create

File Description Complexity
frontend/src/pages/DNSProviders.tsx DNS providers list page Medium
frontend/src/components/DNSProviderForm.tsx Add/edit provider form High
frontend/src/components/DNSProviderCard.tsx Provider card component Low

UI Wireframe

┌─────────────────────────────────────────────────────────────────┐
│ DNS Providers                              [+ Add Provider]     │
│ Configure DNS providers for wildcard certificate issuance      │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  DNS providers are required to issue wildcard certificates │ │
│ │ (e.g., *.example.com) via Let's Encrypt.                    │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│ ┌─────────────────────────┐  ┌─────────────────────────┐       │
│ │ ☁️ Cloudflare           │  │ 🔶 Route 53            │       │
│ │ Production Account      │  │ AWS Dev Account         │       │
│ │ ⭐ Default  ✅ Active   │  │ ✅ Active               │       │
│ │ Last used: 2 hours ago  │  │ Never used              │       │
│ │ Success: 15 | Failed: 0 │  │ Success: 0 | Failed: 0  │       │
│ │ [Edit] [Test] [Delete]  │  │ [Edit] [Test] [Delete]  │       │
│ └─────────────────────────┘  └─────────────────────────┘       │
└─────────────────────────────────────────────────────────────────┘

Phase 3: Integration with Certificates/Proxy Hosts (~1-2 hours)

Objective: Connect DNS providers to certificate workflows

Files to Create

File Description Complexity
frontend/src/components/DNSProviderSelector.tsx Dropdown selector Low

Files to Modify

File Changes Complexity
frontend/src/App.tsx Add /dns-providers route Low
frontend/src/components/layout/Layout.tsx Add navigation link Low
frontend/src/components/ProxyHostForm.tsx Add DNS provider selector for wildcards Medium
frontend/src/locales/en/translation.json Add translation keys Low

8. Security Requirements

Encryption at Rest

  • Algorithm: AES-256-GCM (authenticated encryption)
  • Key: 32-byte key loaded from CHARON_ENCRYPTION_KEY environment variable
  • Format: Base64-encoded ciphertext with prepended nonce

Key Management

# Generate key (one-time setup)
openssl rand -base64 32

# Set environment variable
export CHARON_ENCRYPTION_KEY="<base64-encoded-32-byte-key>"
  • Key MUST be stored in environment variable or secrets manager
  • Key MUST NOT be committed to version control
  • Key rotation support via key_version field (future)

API Security

  • Credentials NEVER returned in API responses
  • Response includes only has_credentials: true/false indicator
  • Update requests with empty credentials preserve existing values
  • Audit logging for all credential access (create, update, decrypt for Caddy)

Database Security

  • credentials_encrypted column excluded from JSON serialization (json:"-")
  • Database backups should be encrypted separately
  • Consider column-level encryption for additional defense-in-depth

9. Testing Strategy

Backend Unit Tests (>85% Coverage)

Test File Coverage Target Key Test Cases
crypto/encryption_test.go 100% Encrypt/decrypt roundtrip, invalid key, tampered ciphertext
models/dns_provider_test.go 90% Model validation, table name
services/dns_provider_service_test.go 85% CRUD operations, encryption integration, error handling
handlers/dns_provider_handler_test.go 85% HTTP methods, validation errors, auth required

Frontend Unit Tests (>85% Coverage)

Test File Coverage Target Key Test Cases
api/dnsProviders.test.ts 90% API calls, error handling
hooks/useDNSProviders.test.ts 85% Query/mutation behavior
pages/DNSProviders.test.tsx 80% Render states, user interactions
components/DNSProviderForm.test.tsx 85% Form validation, submission

Integration Tests

Test Description
integration/dns_provider_test.go Full CRUD flow with database
integration/caddy_dns_challenge_test.go Config generation with DNS provider

Manual Test Scenarios

  1. Happy Path:

    • Create Cloudflare provider with valid API token
    • Test connection (expect success)
    • Create proxy host with *.example.com
    • Verify Caddy requests DNS challenge
    • Confirm certificate issued
  2. Error Handling:

    • Create provider with invalid credentials → test fails
    • Delete provider in use by proxy host → error message
    • Attempt wildcard without DNS provider → validation error
  3. Security:

    • GET provider → credentials NOT in response
    • Update provider without credentials → preserves existing
    • Audit log contains credential access events

10. Documentation Deliverables

User Guide: DNS Providers

Location: docs/guides/dns-providers.md

Contents:

  • What are DNS providers and why they're needed
  • Setting up your first DNS provider
  • Managing multiple providers
  • Troubleshooting common issues

Provider-Specific Setup Guides

Location: docs/guides/dns-providers/

File Provider
cloudflare.md Cloudflare (API token creation, permissions)
route53.md AWS Route 53 (IAM policy, credentials)
digitalocean.md DigitalOcean (token generation)
google-cloud-dns.md Google Cloud DNS (service account setup)
azure-dns.md Azure DNS (app registration, permissions)

Troubleshooting Guide

Location: docs/troubleshooting/dns-challenges.md

Contents:

  • DNS propagation delays
  • Permission/authentication errors
  • Firewall considerations
  • Debug logging

11. Risk Assessment

Technical Risks

Risk Likelihood Impact Mitigation
Encryption key loss Low Critical Document key backup procedures, test recovery
DNS provider API changes Medium Medium Abstract provider logic, version-specific adapters
Caddy DNS module incompatibility Low High Test against specific Caddy version, pin dependencies
Credential exposure in logs Medium High Audit all logging, mask sensitive fields
Performance impact of encryption Low Low AES-NI hardware acceleration, minimal overhead

Mitigations

  1. Key Loss: Require key backup during initial setup, document recovery procedures
  2. API Changes: Use provider abstraction layer, monitor upstream changes
  3. Caddy Compatibility: Pin Caddy version, comprehensive integration tests
  4. Log Exposure: Structured logging with field masking, security audit
  5. Performance: Benchmark encryption operations, consider caching decrypted creds briefly

12. Phased Delivery Timeline

Phase Description Estimated Time Dependencies
Phase 1 Foundation (Encryption pkg, DNSProvider model, migrations) 2-3 hours None
Phase 2 Backend Service + API (CRUD handlers, validation) 2-3 hours Phase 1
Phase 3 Caddy Integration (DNS challenge config generation) 2 hours Phase 2
Phase 4 Frontend UI (Pages, forms, integration) 3-4 hours Phase 2 API
Phase 5 Testing & Documentation (Unit tests, guides) 2-3 hours All phases

Total Estimated Time: 11-15 hours

Dependency Graph

Phase 1 (Foundation)
    │
    ├──► Phase 2 (Backend API)
    │        │
    │        ├──► Phase 3 (Caddy Integration)
    │        │
    │        └──► Phase 4 (Frontend UI)
    │                 │
    └─────────────────┴──► Phase 5 (Testing & Docs)

13. Files to Create

Backend

Path Description
backend/internal/crypto/encryption.go AES-256-GCM encryption service
backend/internal/crypto/encryption_test.go Encryption unit tests
backend/internal/models/dns_provider.go DNSProvider model definition
backend/internal/services/dns_provider_service.go DNS provider business logic
backend/internal/services/dns_provider_service_test.go Service unit tests
backend/internal/api/handlers/dns_provider_handler.go HTTP handlers
backend/internal/api/handlers/dns_provider_handler_test.go Handler unit tests
backend/integration/dns_provider_test.go Integration tests

Frontend

Path Description
frontend/src/api/dnsProviders.ts API client functions
frontend/src/hooks/useDNSProviders.ts React Query hooks
frontend/src/data/dnsProviderSchemas.ts Provider field definitions
frontend/src/pages/DNSProviders.tsx DNS providers page
frontend/src/components/DNSProviderForm.tsx Add/edit form
frontend/src/components/DNSProviderCard.tsx Provider card component
frontend/src/components/DNSProviderSelector.tsx Dropdown selector

Documentation

Path Description
docs/guides/dns-providers.md User guide
docs/guides/dns-providers/cloudflare.md Cloudflare setup
docs/guides/dns-providers/route53.md AWS Route 53 setup
docs/guides/dns-providers/digitalocean.md DigitalOcean setup
docs/troubleshooting/dns-challenges.md Troubleshooting guide

14. Files to Modify

Backend

Path Changes
backend/internal/config/config.go Add EncryptionKey field
backend/internal/models/proxy_host.go Add DNSProviderID, UseDNSChallenge fields
backend/internal/caddy/types.go Add DNSChallengeConfig, ChallengesConfig types
backend/internal/caddy/config.go Add DNS challenge issuer generation
backend/internal/caddy/manager.go Load DNS providers when applying config
backend/internal/api/routes/routes.go Register DNS provider routes
backend/internal/api/handlers/proxyhost_handler.go Handle DNS provider association
backend/cmd/server/main.go Initialize encryption service

Frontend

Path Changes
frontend/src/App.tsx Add /dns-providers route
frontend/src/components/layout/Layout.tsx Add navigation link to DNS Providers
frontend/src/components/ProxyHostForm.tsx Add DNS provider selector for wildcard domains
frontend/src/locales/en/translation.json Add dnsProviders.* translation keys

15. Definition of Done Checklist

Backend

  • crypto/encryption.go implemented with AES-256-GCM
  • DNSProvider model created with all fields
  • Database migration created and tested
  • DNSProviderService implements full CRUD
  • Credentials encrypted on save, decrypted on demand
  • API handlers for all endpoints
  • Input validation on all endpoints
  • Credentials never exposed in API responses
  • Unit tests pass with ≥85% coverage
  • Integration tests pass

Caddy Integration

  • DNS challenge config generated correctly
  • ProxyHost correctly associated with DNSProvider
  • Wildcard domains use DNS-01 challenge
  • Non-wildcard domains continue using HTTP-01

Frontend

  • API client functions implemented
  • React Query hooks working
  • DNS Providers page lists all providers
  • Add/Edit form with dynamic fields per provider
  • Test connection button functional
  • Provider selector in ProxyHost form
  • Wildcard domain detection triggers DNS provider requirement
  • All translations added
  • Unit tests pass with ≥85% coverage

Security

  • Encryption key documented in setup guide
  • Credentials encrypted at rest verified
  • API responses verified to exclude credentials
  • Audit logging for credential operations
  • Security review completed

Documentation

  • User guide written
  • Provider-specific guides written (at least Cloudflare, Route53)
  • Troubleshooting guide written
  • API documentation updated
  • CHANGELOG updated

Final Validation

  • End-to-end test: Create DNS provider → Create wildcard proxy → Certificate issued
  • Error scenarios tested (invalid creds, deleted provider)
  • UI reviewed for accessibility
  • Performance acceptable (no noticeable delays)

Consolidated from backend and frontend research documents Ready for implementation