1// Package conoha implements a DNS provider for solving the DNS-01 challenge using ConoHa DNS. 2package conoha 3 4import ( 5 "errors" 6 "fmt" 7 "net/http" 8 "time" 9 10 "github.com/go-acme/lego/v3/challenge/dns01" 11 "github.com/go-acme/lego/v3/platform/config/env" 12 "github.com/go-acme/lego/v3/providers/dns/conoha/internal" 13) 14 15// Environment variables names. 16const ( 17 envNamespace = "CONOHA_" 18 19 EnvRegion = envNamespace + "REGION" 20 EnvTenantID = envNamespace + "TENANT_ID" 21 EnvAPIUsername = envNamespace + "API_USERNAME" 22 EnvAPIPassword = envNamespace + "API_PASSWORD" 23 24 EnvTTL = envNamespace + "TTL" 25 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" 26 EnvPollingInterval = envNamespace + "POLLING_INTERVAL" 27 EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" 28) 29 30// Config is used to configure the creation of the DNSProvider. 31type Config struct { 32 Region string 33 TenantID string 34 Username string 35 Password string 36 TTL int 37 PropagationTimeout time.Duration 38 PollingInterval time.Duration 39 HTTPClient *http.Client 40} 41 42// NewDefaultConfig returns a default configuration for the DNSProvider. 43func NewDefaultConfig() *Config { 44 return &Config{ 45 Region: env.GetOrDefaultString(EnvRegion, "tyo1"), 46 TTL: env.GetOrDefaultInt(EnvTTL, 60), 47 PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), 48 PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), 49 HTTPClient: &http.Client{ 50 Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), 51 }, 52 } 53} 54 55// DNSProvider implements the challenge.Provider interface. 56type DNSProvider struct { 57 config *Config 58 client *internal.Client 59} 60 61// NewDNSProvider returns a DNSProvider instance configured for ConoHa DNS. 62// Credentials must be passed in the environment variables: 63// CONOHA_TENANT_ID, CONOHA_API_USERNAME, CONOHA_API_PASSWORD. 64func NewDNSProvider() (*DNSProvider, error) { 65 values, err := env.Get(EnvTenantID, EnvAPIUsername, EnvAPIPassword) 66 if err != nil { 67 return nil, fmt.Errorf("conoha: %w", err) 68 } 69 70 config := NewDefaultConfig() 71 config.TenantID = values[EnvTenantID] 72 config.Username = values[EnvAPIUsername] 73 config.Password = values[EnvAPIPassword] 74 75 return NewDNSProviderConfig(config) 76} 77 78// NewDNSProviderConfig return a DNSProvider instance configured for ConoHa DNS. 79func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { 80 if config == nil { 81 return nil, errors.New("conoha: the configuration of the DNS provider is nil") 82 } 83 84 if config.TenantID == "" || config.Username == "" || config.Password == "" { 85 return nil, errors.New("conoha: some credentials information are missing") 86 } 87 88 auth := internal.Auth{ 89 TenantID: config.TenantID, 90 PasswordCredentials: internal.PasswordCredentials{ 91 Username: config.Username, 92 Password: config.Password, 93 }, 94 } 95 96 client, err := internal.NewClient(config.Region, auth, config.HTTPClient) 97 if err != nil { 98 return nil, fmt.Errorf("conoha: failed to create client: %w", err) 99 } 100 101 return &DNSProvider{config: config, client: client}, nil 102} 103 104// Present creates a TXT record to fulfill the dns-01 challenge. 105func (d *DNSProvider) Present(domain, token, keyAuth string) error { 106 fqdn, value := dns01.GetRecord(domain, keyAuth) 107 108 authZone, err := dns01.FindZoneByFqdn(fqdn) 109 if err != nil { 110 return err 111 } 112 113 id, err := d.client.GetDomainID(authZone) 114 if err != nil { 115 return fmt.Errorf("conoha: failed to get domain ID: %w", err) 116 } 117 118 record := internal.Record{ 119 Name: fqdn, 120 Type: "TXT", 121 Data: value, 122 TTL: d.config.TTL, 123 } 124 125 err = d.client.CreateRecord(id, record) 126 if err != nil { 127 return fmt.Errorf("conoha: failed to create record: %w", err) 128 } 129 130 return nil 131} 132 133// CleanUp clears ConoHa DNS TXT record. 134func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { 135 fqdn, value := dns01.GetRecord(domain, keyAuth) 136 137 authZone, err := dns01.FindZoneByFqdn(fqdn) 138 if err != nil { 139 return err 140 } 141 142 domID, err := d.client.GetDomainID(authZone) 143 if err != nil { 144 return fmt.Errorf("conoha: failed to get domain ID: %w", err) 145 } 146 147 recID, err := d.client.GetRecordID(domID, fqdn, "TXT", value) 148 if err != nil { 149 return fmt.Errorf("conoha: failed to get record ID: %w", err) 150 } 151 152 err = d.client.DeleteRecord(domID, recID) 153 if err != nil { 154 return fmt.Errorf("conoha: failed to delete record: %w", err) 155 } 156 157 return nil 158} 159 160// Timeout returns the timeout and interval to use when checking for DNS propagation. 161// Adjusting here to cope with spikes in propagation times. 162func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { 163 return d.config.PropagationTimeout, d.config.PollingInterval 164} 165