- Add 97 test cases covering API, hooks, and components - Achieve 87.8% frontend coverage (exceeds 85% requirement) - Fix CodeQL informational findings - Ensure type safety and code quality standards Resolves coverage failure in PR #460
33 KiB
DNS Challenge Support Implementation Spec
Issue: #21 - DNS Challenge Support for Wildcard Certificates Priority: Critical (Beta Release Blocker) Version: 1.0 Date: January 1, 2026
1. Executive Summary
Wildcard SSL certificates (e.g., *.example.com) are essential for modern multi-tenant and subdomain-heavy deployments, but Let's Encrypt and other ACME providers require DNS-01 challenges to issue them. Currently, Charon only supports HTTP-01 challenges, which cannot validate wildcard domains. This limitation blocks users who need flexible subdomain management from using automatic certificate issuance.
This feature is critical for the beta release because wildcard certificate support is a fundamental expectation of any production-grade reverse proxy manager. Without it, users must manually provision and upload certificates, defeating the purpose of automated TLS management that Charon promises.
The implementation involves creating a secure credential storage system with AES-256-GCM encryption, a new DNSProvider entity with full CRUD operations, extending the Caddy configuration generator to emit DNS challenge blocks, and building a complete frontend management interface. The approach prioritizes security (encrypted credentials at rest), extensibility (supporting 10+ major DNS providers), and user experience (test-before-save, clear status indicators).
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-providers → 200 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/types → 200 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_KEYenvironment 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_versionfield (future)
API Security
- Credentials NEVER returned in API responses
- Response includes only
has_credentials: true/falseindicator - Update requests with empty
credentialspreserve existing values - Audit logging for all credential access (create, update, decrypt for Caddy)
Database Security
credentials_encryptedcolumn 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
-
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
-
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
-
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
- Key Loss: Require key backup during initial setup, document recovery procedures
- API Changes: Use provider abstraction layer, monitor upstream changes
- Caddy Compatibility: Pin Caddy version, comprehensive integration tests
- Log Exposure: Structured logging with field masking, security audit
- 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.goimplemented with AES-256-GCMDNSProvidermodel created with all fields- Database migration created and tested
DNSProviderServiceimplements 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