Files
Charon/backend/pkg/dnsprovider/custom/manual_provider.go
2026-03-04 18:34:49 +00:00

178 lines
6.2 KiB
Go

// Package custom provides custom DNS provider plugins for non-built-in integrations.
package custom
import (
"fmt"
"strconv"
"time"
"github.com/Wikid82/charon/backend/pkg/dnsprovider"
)
// Default configuration values for the manual provider.
const (
DefaultTimeoutMinutes = 10
DefaultPollingIntervalSeconds = 30
MinTimeoutMinutes = 1
MaxTimeoutMinutes = 60
MinPollingIntervalSeconds = 5
MaxPollingIntervalSeconds = 120
)
// ManualProvider implements the ProviderPlugin interface for manual DNS challenges.
// Users manually create TXT records at their DNS provider and click verify.
type ManualProvider struct {
timeoutMinutes int
pollingIntervalSeconds int
}
// NewManualProvider creates a new ManualProvider with default settings.
func NewManualProvider() *ManualProvider {
return &ManualProvider{
timeoutMinutes: DefaultTimeoutMinutes,
pollingIntervalSeconds: DefaultPollingIntervalSeconds,
}
}
// Type returns the unique provider type identifier.
func (p *ManualProvider) Type() string {
return "manual"
}
// Metadata returns descriptive information about the provider.
func (p *ManualProvider) Metadata() dnsprovider.ProviderMetadata {
return dnsprovider.ProviderMetadata{
Type: "manual",
Name: "Manual (No Automation)",
Description: "Manually create DNS TXT records for ACME challenges. Suitable for testing or providers without API access.",
DocumentationURL: "https://charon.dev/docs/features/manual-dns-challenge",
IsBuiltIn: false,
Version: "1.0.0",
InterfaceVersion: dnsprovider.InterfaceVersion,
}
}
// Init is called after the plugin is registered.
func (p *ManualProvider) Init() error {
return nil
}
// Cleanup is called before the plugin is unregistered.
func (p *ManualProvider) Cleanup() error {
return nil
}
// RequiredCredentialFields returns credential fields that must be provided.
// Manual provider has no required credentials.
func (p *ManualProvider) RequiredCredentialFields() []dnsprovider.CredentialFieldSpec {
return []dnsprovider.CredentialFieldSpec{}
}
// OptionalCredentialFields returns credential fields that may be provided.
func (p *ManualProvider) OptionalCredentialFields() []dnsprovider.CredentialFieldSpec {
return []dnsprovider.CredentialFieldSpec{
{
Name: "timeout_minutes",
Label: "Challenge Timeout (minutes)",
Type: "text",
Placeholder: "10",
Hint: fmt.Sprintf("Time before challenge expires (%d-%d minutes, default: %d)", MinTimeoutMinutes, MaxTimeoutMinutes, DefaultTimeoutMinutes),
},
{
Name: "polling_interval_seconds",
Label: "DNS Check Interval (seconds)",
Type: "text",
Placeholder: "30",
Hint: fmt.Sprintf("How often to check DNS propagation (%d-%d seconds, default: %d)", MinPollingIntervalSeconds, MaxPollingIntervalSeconds, DefaultPollingIntervalSeconds),
},
}
}
// ValidateCredentials checks if the provided credentials are valid.
func (p *ManualProvider) ValidateCredentials(creds map[string]string) error {
// Validate timeout if provided
if timeoutStr := creds["timeout_minutes"]; timeoutStr != "" {
timeout, err := strconv.Atoi(timeoutStr)
if err != nil {
return fmt.Errorf("timeout_minutes must be a number: %w", err)
}
if timeout < MinTimeoutMinutes || timeout > MaxTimeoutMinutes {
return fmt.Errorf("timeout_minutes must be between %d and %d", MinTimeoutMinutes, MaxTimeoutMinutes)
}
}
// Validate polling interval if provided
if intervalStr := creds["polling_interval_seconds"]; intervalStr != "" {
interval, err := strconv.Atoi(intervalStr)
if err != nil {
return fmt.Errorf("polling_interval_seconds must be a number: %w", err)
}
if interval < MinPollingIntervalSeconds || interval > MaxPollingIntervalSeconds {
return fmt.Errorf("polling_interval_seconds must be between %d and %d", MinPollingIntervalSeconds, MaxPollingIntervalSeconds)
}
}
return nil
}
// TestCredentials attempts to verify credentials work.
// For manual provider, this always succeeds since there's no external API.
func (p *ManualProvider) TestCredentials(creds map[string]string) error {
return p.ValidateCredentials(creds)
}
// SupportsMultiCredential indicates if the provider can handle zone-specific credentials.
func (p *ManualProvider) SupportsMultiCredential() bool {
return false
}
// BuildCaddyConfig constructs the Caddy DNS challenge configuration.
// For manual provider, this returns a marker that tells Caddy to use manual mode.
func (p *ManualProvider) BuildCaddyConfig(creds map[string]string) map[string]any {
// Manual provider doesn't integrate with Caddy's DNS challenge directly.
// Instead, Charon handles the challenge flow and signals completion.
return map[string]any{
"name": "manual",
"manual": true,
}
}
// BuildCaddyConfigForZone constructs config for a specific zone.
func (p *ManualProvider) BuildCaddyConfigForZone(baseDomain string, creds map[string]string) map[string]any {
return p.BuildCaddyConfig(creds)
}
// PropagationTimeout returns the recommended DNS propagation wait time.
func (p *ManualProvider) PropagationTimeout() time.Duration {
return time.Duration(p.timeoutMinutes) * time.Minute
}
// PollingInterval returns the recommended polling interval for DNS verification.
func (p *ManualProvider) PollingInterval() time.Duration {
return time.Duration(p.pollingIntervalSeconds) * time.Second
}
// GetTimeoutMinutes returns the configured timeout in minutes.
func (p *ManualProvider) GetTimeoutMinutes(creds map[string]string) int {
if timeoutStr := creds["timeout_minutes"]; timeoutStr != "" {
if timeout, err := strconv.Atoi(timeoutStr); err == nil {
if timeout >= MinTimeoutMinutes && timeout <= MaxTimeoutMinutes {
return timeout
}
}
}
return DefaultTimeoutMinutes
}
// GetPollingIntervalSeconds returns the configured polling interval in seconds.
func (p *ManualProvider) GetPollingIntervalSeconds(creds map[string]string) int {
if intervalStr := creds["polling_interval_seconds"]; intervalStr != "" {
if interval, err := strconv.Atoi(intervalStr); err == nil {
if interval >= MinPollingIntervalSeconds && interval <= MaxPollingIntervalSeconds {
return interval
}
}
}
return DefaultPollingIntervalSeconds
}