- Updated the QA/Security Validation Report with new dates and status. - Enhanced coverage verification metrics for backend and frontend tests. - Improved TypeScript checks and security scans, ensuring all checks passed. - Refactored ProxyHosts tests to utilize mock implementations for hooks and APIs. - Added smoke test for login functionality using Playwright. - Adjusted vitest configuration to use thread pooling for tests. - Removed unnecessary peer dependency from package-lock.json.
38 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).
1.1 Trivy Remediation Addendum (QA Blocker)
QA found Trivy blockers due to (a) a Dockerfile misconfig check and (b) Trivy scanning local/cache directories inside the workspace mount, causing false positives (fixture secrets + cached dependency CVEs) and scanner errors.
Objectives (short)
- Make Trivy results correct and actionable (scan the repo, not local caches).
- Make findings fail the run (exit code 1) while keeping defaults reasonable for developers.
Remediation plan (execution-ready)
-
Dockerfile: fix AVD-DS-0002 (missing non-root
USER)- Minimal change: add a final
USER charonin the root Dockerfile. - Permission handling: ensure runtime write paths remain owned by
charon(already mostly handled viachown; confirm/app/data,/config, and any log dirs are writable). - Runtime constraints to resolve explicitly:
- Privileged ports (80/443): if running as non-root, ensure the server can still bind these (either grant
cap_net_bind_serviceto the relevant binaries during build, or adjust runtime to bind high ports and rely on port mapping). - Docker socket integration: if Docker features require root to mutate
/var/run/docker.sockownership, update entrypoint logic so it can run non-root by default (e.g., rely on--group-add/matching socket GID, or gracefully disable Docker integration when permissions are insufficient).
- Privileged ports (80/443): if running as non-root, ensure the server can still bind these (either grant
- Minimal change: add a final
-
Fix Trivy scan correctness: exclude cache/db directories from scan scope
- Update .github/skills/security-scan-trivy-scripts/run.sh to add explicit directory skips so Trivy doesn’t scan dependency fixtures and local tool databases:
.cache/(includes.cache/go/pkg/mod/...fixture secrets and cached deps)codeql-db-go/andcodeql-db-js/(CodeQL databases)my-codeql-db/codeql-agent-results/codeql-custom-queries-go/(optional, for scan speed/noise)test-results/(optional; include only if Trivy flags test artifacts)
- Implementation approach: prefer scan-root-relative paths with explicit directory names (e.g.,
trivy fs . --skip-dirs .cache --skip-dirs codeql-db-go --skip-dirs codeql-db-js ...). Avoid glob patterns in scan inputs and skip lists; keep arguments explicit.
- Ensure findings fail the scan, without unnecessary workflow breakage
- In .github/skills/security-scan-trivy-scripts/run.sh:
- Add
--exit-code 1so findings fail. - Set a default severity threshold to reduce noise:
CRITICAL,HIGH(allow local override viaTRIVY_SEVERITY).
- Add
- Add a repo-level ignore policy:
- Create/standardize
.trivyignore(or.trivyignore.yaml) with only documented, justified suppressions (include a link to a tracking issue and an “expires on” date). - Keep CI strict: ignorefile allowed for known false positives only; never blanket-ignore
.cache/via ignorefile—skip dirs instead.
- Create/standardize
- Pin Trivy version + address scanner/policy errors
- Replace
aquasec/trivy:latestwith a pinned tag in the Trivy skill runner:- Introduce
TRIVY_IMAGE(default pinned, e.g.,aquasec/trivy:<pin>), and document how/when to bump.
- Introduce
- Rego policy conflict + Dockerfile scanner errors observed in QA:
- Dockerfile scanner error was triggered by parsing non-project Dockerfiles inside
.cache/go/pkg/mod/...; directory exclusions above should eliminate this. - If the Rego conflict persists even after pinning and exclusions, split the scan into two steps:
trivy fsforvuln,secreton the repo (with skipped dirs)trivy fsformisconfigon only the project’s Docker/compose files by passing explicit paths (e.g.,Dockerfileand.docker/compose/) to minimize policy evaluation surface (no globs).
- Dockerfile scanner error was triggered by parsing non-project Dockerfiles inside
- Replace
Files likely involved
- Dockerfile
- .github/skills/security-scan-trivy-scripts/run.sh
- scripts/trivy-scan.sh (deprecated; still references
aquasec/trivy:latest) - Makefile (has Trivy commands/targets)
- .github/workflows/docker-build.yml (already uses
--exit-code 1in at least one Trivy step; keep local behavior aligned)
Validation commands / tasks
- VS Code task:
shell: Security: Trivy Scan - Direct skill run:
.github/skills/scripts/skill-runner.sh security-scan-trivy - After Dockerfile remediation:
shell: Build & Run: Local Docker Imageand confirm the container starts and serves HTTP/HTTPS as expected.
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