diff --git a/README.md b/README.md index def4aa4c..4b06b101 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,13 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). | [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Joker](https://go-acme.github.io/lego/dns/joker/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) | | [Linode (deprecated)](https://go-acme.github.io/lego/dns/linode/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linodev4/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | -| [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | -| [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | -| [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | -| [reg.ru](https://go-acme.github.io/lego/dns/regru/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | -| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | -| [TransIP](https://go-acme.github.io/lego/dns/transip/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | -| [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | +| [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | +| [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) | +| [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | +| [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [reg.ru](https://go-acme.github.io/lego/dns/regru/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | +| [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | +| [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | +| [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | +| [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | | | diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 8fc07f6d..2f853a4e 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -57,6 +57,7 @@ func allDNSCodes() string { "linodev4", "liquidweb", "mydnsjp", + "mythicbeasts", "namecheap", "namedotcom", "namesilo", @@ -1003,6 +1004,29 @@ func displayDNSHelp(name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/mydnsjp`) + case "mythicbeasts": + // generated from: providers/dns/mythicbeasts/mythicbeasts.toml + ew.writeln(`Configuration for MythicBeasts.`) + ew.writeln(`Code: 'mythicbeasts'`) + ew.writeln(`Since: 'v0.3.7'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "MYTHICBEASTS_PASSWORD": Paswword`) + ew.writeln(` - "MYTHICBEASTS_USERNAME": User name`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "MYTHICBEASTS_API_ENDPOINT": The endpoint for the API (must implement v2)`) + ew.writeln(` - "MYTHICBEASTS_HTTP_TIMEOUT": API request timeout`) + ew.writeln(` - "MYTHICBEASTS_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "MYTHICBEASTS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "MYTHICBEASTS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "MYTHICBEASYS_AUTH_API_ENDPOINT": The endpoint for Mythic Beasts' Authentication`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/mythicbeasts`) + case "namecheap": // generated from: providers/dns/namecheap/namecheap.toml ew.writeln(`Configuration for Namecheap.`) diff --git a/docs/content/dns/zz_gen_mythicbeasts.md b/docs/content/dns/zz_gen_mythicbeasts.md new file mode 100644 index 00000000..bc002fc9 --- /dev/null +++ b/docs/content/dns/zz_gen_mythicbeasts.md @@ -0,0 +1,69 @@ +--- +title: "MythicBeasts" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: mythicbeasts +--- + + + + + +Since: v0.3.7 + +Configuration for [MythicBeasts](https://www.mythic-beasts.com/). + + + + +- Code: `mythicbeasts` + +Here is an example bash command using the MythicBeasts provider: + +```bash +MYTHICBEASTS_USER_NAME=myuser \ +MYTHICBEASTS_PASSWORD=mypass \ +lego --dns mythicbeasts --domains my.domain.com --email my@email.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `MYTHICBEASTS_PASSWORD` | Paswword | +| `MYTHICBEASTS_USERNAME` | User name | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here](/lego/dns/#configuration-and-credentials). + + +## Additional Configuration + +| Environment Variable Name | Description | +|--------------------------------|-------------| +| `MYTHICBEASTS_API_ENDPOINT` | The endpoint for the API (must implement v2) | +| `MYTHICBEASTS_HTTP_TIMEOUT` | API request timeout | +| `MYTHICBEASTS_POLLING_INTERVAL` | Time between DNS propagation check | +| `MYTHICBEASTS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `MYTHICBEASTS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `MYTHICBEASYS_AUTH_API_ENDPOINT` | The endpoint for Mythic Beasts' Authentication | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here](/lego/dns/#configuration-and-credentials). + +If you are using specific API keys, then the username is the API ID for your API key, and the password is the API secret. + +Your API key name is not needed to operate lego. + + + +## More information + +- [API documentation](https://www.mythic-beasts.com/support/api/dnsv2) + + + + diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index a17a1eca..468955b0 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -48,6 +48,7 @@ import ( "github.com/go-acme/lego/v3/providers/dns/linodev4" "github.com/go-acme/lego/v3/providers/dns/liquidweb" "github.com/go-acme/lego/v3/providers/dns/mydnsjp" + "github.com/go-acme/lego/v3/providers/dns/mythicbeasts" "github.com/go-acme/lego/v3/providers/dns/namecheap" "github.com/go-acme/lego/v3/providers/dns/namedotcom" "github.com/go-acme/lego/v3/providers/dns/namesilo" @@ -169,6 +170,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return dns01.NewDNSProviderManual() case "mydnsjp": return mydnsjp.NewDNSProvider() + case "mythicbeasts": + return mythicbeasts.NewDNSProvider() case "namecheap": return namecheap.NewDNSProvider() case "namedotcom": diff --git a/providers/dns/mythicbeasts/client.go b/providers/dns/mythicbeasts/client.go new file mode 100644 index 00000000..75ba6ac7 --- /dev/null +++ b/providers/dns/mythicbeasts/client.go @@ -0,0 +1,235 @@ +package mythicbeasts + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "path" + "strings" +) + +const ( + apiBaseURL = "https://api.mythic-beasts.com/beta/dns" + authBaseURL = "https://auth.mythic-beasts.com/login" +) + +type authResponse struct { + // The bearer token for use in API requests + Token string `json:"access_token"` + + // The maximum lifetime of the token in seconds + Lifetime int `json:"expires_in"` + + // The token type (must be 'bearer') + TokenType string `json:"token_type"` +} + +type authResponseError struct { + ErrorMsg string `json:"error"` + ErrorDescription string `json:"error_description"` +} + +func (a authResponseError) Error() string { + return fmt.Sprintf("%s: %s", a.ErrorMsg, a.ErrorDescription) +} + +type createTXTRequest struct { + Records []createTXTRecord `json:"records"` +} + +type createTXTRecord struct { + Host string `json:"host"` + TTL int `json:"ttl"` + Type string `json:"type"` + Data string `json:"data"` +} + +type createTXTResponse struct { + Added int `json:"records_added"` + Removed int `json:"records_removed"` + Message string `json:"message"` +} + +type deleteTXTResponse struct { + Removed int `json:"records_removed"` + Message string `json:"message"` +} + +// Logs into mythic beasts and acquires a bearer token for use in future API calls. +// https://www.mythic-beasts.com/support/api/auth#sec-obtaining-a-token +func (d *DNSProvider) login() error { + if d.token != "" { + // Already authenticated, stop now + return nil + } + + reqBody := strings.NewReader("grant_type=client_credentials") + + req, err := http.NewRequest(http.MethodPost, d.config.AuthAPIEndpoint.String(), reqBody) + if err != nil { + return err + } + + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.SetBasicAuth(d.config.UserName, d.config.Password) + + resp, err := d.config.HTTPClient.Do(req) + if err != nil { + return err + } + + defer func() { _ = resp.Body.Close() }() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("login: %w", err) + } + + if resp.StatusCode != 200 { + if resp.StatusCode < 400 || resp.StatusCode > 499 { + return fmt.Errorf("login: unknown error in auth API: %d", resp.StatusCode) + } + + // Returned body should be a JSON thing + errResp := &authResponseError{} + err = json.Unmarshal(body, errResp) + if err != nil { + return fmt.Errorf("login: error parsing error: %w", err) + } + + return fmt.Errorf("login: %d: %w", resp.StatusCode, errResp) + } + + authResp := authResponse{} + err = json.Unmarshal(body, &authResp) + if err != nil { + return fmt.Errorf("login: error parsing response: %w", err) + } + + if authResp.TokenType != "bearer" { + return fmt.Errorf("login: received unexpected token type: %s", authResp.TokenType) + } + + d.token = authResp.Token + + // Success + return nil +} + +// https://www.mythic-beasts.com/support/api/dnsv2#ep-get-zoneszonerecords +func (d *DNSProvider) createTXTRecord(zone string, leaf string, value string) error { + if d.token == "" { + return fmt.Errorf("createTXTRecord: not logged in") + } + + createReq := createTXTRequest{ + Records: []createTXTRecord{{ + Host: leaf, + TTL: d.config.TTL, + Type: "TXT", + Data: value, + }}, + } + + reqBody, err := json.Marshal(createReq) + if err != nil { + return fmt.Errorf("createTXTRecord: marshaling request body failed: %w", err) + } + + endpoint, err := d.config.APIEndpoint.Parse(path.Join(d.config.APIEndpoint.Path, "zones", zone, "records", leaf, "TXT")) + if err != nil { + return fmt.Errorf("createTXTRecord: failed to parse URL: %w", err) + } + + req, err := http.NewRequest(http.MethodPost, endpoint.String(), bytes.NewReader(reqBody)) + if err != nil { + return fmt.Errorf("createTXTRecord: %w", err) + } + + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", d.token)) + req.Header.Add("Content-Type", "application/json") + + resp, err := d.config.HTTPClient.Do(req) + if err != nil { + return fmt.Errorf("createTXTRecord: unable to perform HTTP request: %w", err) + } + + defer func() { _ = resp.Body.Close() }() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("createTXTRecord: %w", err) + } + + if resp.StatusCode != 200 { + return fmt.Errorf("createTXTRecord: error in API: %d", resp.StatusCode) + } + + createResp := createTXTResponse{} + err = json.Unmarshal(body, &createResp) + if err != nil { + return fmt.Errorf("createTXTRecord: error parsing response: %w", err) + } + + if createResp.Added != 1 { + return errors.New("createTXTRecord: did not add TXT record for some reason") + } + + // Success + return nil +} + +// https://www.mythic-beasts.com/support/api/dnsv2#ep-delete-zoneszonerecords +func (d *DNSProvider) removeTXTRecord(zone string, leaf string, value string) error { + if d.token == "" { + return fmt.Errorf("removeTXTRecord: not logged in") + } + + endpoint, err := d.config.APIEndpoint.Parse(path.Join(d.config.APIEndpoint.Path, "zones", zone, "records", leaf, "TXT")) + if err != nil { + return fmt.Errorf("createTXTRecord: failed to parse URL: %w", err) + } + + query := endpoint.Query() + query.Add("data", value) + endpoint.RawQuery = query.Encode() + + req, err := http.NewRequest(http.MethodDelete, endpoint.String(), nil) + if err != nil { + return fmt.Errorf("removeTXTRecord: %w", err) + } + + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", d.token)) + + resp, err := d.config.HTTPClient.Do(req) + if err != nil { + return fmt.Errorf("removeTXTRecord: unable to perform HTTP request: %w", err) + } + + defer func() { _ = resp.Body.Close() }() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("removeTXTRecord: %w", err) + } + + if resp.StatusCode != 200 { + return fmt.Errorf("removeTXTRecord: error in API: %d", resp.StatusCode) + } + + deleteResp := deleteTXTResponse{} + err = json.Unmarshal(body, &deleteResp) + if err != nil { + return fmt.Errorf("removeTXTRecord: error parsing response: %w", err) + } + + if deleteResp.Removed != 1 { + return errors.New("deleteTXTRecord: did not add TXT record for some reason") + } + + // Success + return nil +} diff --git a/providers/dns/mythicbeasts/mythicbeasts.go b/providers/dns/mythicbeasts/mythicbeasts.go new file mode 100644 index 00000000..2de817ca --- /dev/null +++ b/providers/dns/mythicbeasts/mythicbeasts.go @@ -0,0 +1,155 @@ +// Package mythicbeasts implements a DNS provider for solving the DNS-01 challenge using Mythic Beasts API. +package mythicbeasts + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v3/challenge/dns01" + "github.com/go-acme/lego/v3/platform/config/env" +) + +// Environment variables names. +const ( + envNamespace = "MYTHICBEASTS_" + + EnvUserName = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + EnvAPIEndpoint = envNamespace + "API_ENDPOINT" + EnvAuthAPIEndpoint = envNamespace + "AUTH_API_ENDPOINT" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +// Config is used to configure the creation of the DNSProvider +type Config struct { + UserName string + Password string + HTTPClient *http.Client + PropagationTimeout time.Duration + PollingInterval time.Duration + APIEndpoint *url.URL + AuthAPIEndpoint *url.URL + TTL int +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() (*Config, error) { + apiEndpoint, err := url.Parse(env.GetOrDefaultString(EnvAPIEndpoint, apiBaseURL)) + if err != nil { + return nil, fmt.Errorf("mythicbeasts: Unable to parse API URL: %w", err) + } + + authEndpoint, err := url.Parse(env.GetOrDefaultString(EnvAuthAPIEndpoint, authBaseURL)) + if err != nil { + return nil, fmt.Errorf("mythicbeasts: Unable to parse AUTH API URL: %w", err) + } + + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + APIEndpoint: apiEndpoint, + AuthAPIEndpoint: authEndpoint, + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), + }, + }, nil +} + +// DNSProvider is an implementation of the challenge.Provider interface that uses +// Mythic Beasts' DNSv2 API to manage TXT records for a domain. +type DNSProvider struct { + config *Config + token string +} + +// NewDNSProvider returns a DNSProvider instance configured for mythicbeasts DNSv2 API. +// Credentials must be passed in the environment variables: +// MYTHICBEASTS_USER_NAME and MYTHICBEASTS_PASSWORD. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUserName, EnvPassword) + if err != nil { + return nil, fmt.Errorf("mythicbeasts: %w", err) + } + + config, err := NewDefaultConfig() + if err != nil { + return nil, fmt.Errorf("mythicbeasts: %w", err) + } + config.UserName = values[EnvUserName] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for mythicbeasts DNSv2 API +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("mythicbeasts: the configuration of the DNS provider is nil") + } + + if config.UserName == "" || config.Password == "" { + return nil, errors.New("mythicbeasts: incomplete credentials, missing username and/or password") + } + + return &DNSProvider{config: config}, nil +} + +// Present creates a TXT record using the specified parameters +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return fmt.Errorf("mythicbeasts: %w", err) + } + + leaf := fqdn[:len(fqdn)-(len(authZone)+1)] + + authZone = dns01.UnFqdn(authZone) + + err = d.login() + if err != nil { + return fmt.Errorf("mythicbeasts: %w", err) + } + + err = d.createTXTRecord(authZone, leaf, value) + if err != nil { + return fmt.Errorf("mythicbeasts: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return fmt.Errorf("mythicbeasts: %w", err) + } + + leaf := fqdn[:len(fqdn)-(len(authZone)+1)] + + authZone = dns01.UnFqdn(authZone) + + err = d.login() + if err != nil { + return fmt.Errorf("mythicbeasts: %w", err) + } + + err = d.removeTXTRecord(authZone, leaf, value) + if err != nil { + return fmt.Errorf("mythicbeasts: %w", err) + } + + return nil +} diff --git a/providers/dns/mythicbeasts/mythicbeasts.toml b/providers/dns/mythicbeasts/mythicbeasts.toml new file mode 100644 index 00000000..49643394 --- /dev/null +++ b/providers/dns/mythicbeasts/mythicbeasts.toml @@ -0,0 +1,33 @@ +Name = "MythicBeasts" +Description = '''''' +URL = "https://www.mythic-beasts.com/" +Code = "mythicbeasts" +Since = "v0.3.7" + +Example = ''' +MYTHICBEASTS_USER_NAME=myuser \ +MYTHICBEASTS_PASSWORD=mypass \ +lego --dns mythicbeasts --domains my.domain.com --email my@email.com run +''' + +Additional = ''' +If you are using specific API keys, then the username is the API ID for your API key, and the password is the API secret. + +Your API key name is not needed to operate lego. +''' + +[Configuration] + [Configuration.Credentials] + MYTHICBEASTS_USERNAME = "User name" + MYTHICBEASTS_PASSWORD = "Paswword" + [Configuration.Additional] + MYTHICBEASTS_API_ENDPOINT = "The endpoint for the API (must implement v2)" + MYTHICBEASYS_AUTH_API_ENDPOINT = "The endpoint for Mythic Beasts' Authentication" + MYTHICBEASTS_POLLING_INTERVAL = "Time between DNS propagation check" + MYTHICBEASTS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + MYTHICBEASTS_TTL = "The TTL of the TXT record used for the DNS challenge" + MYTHICBEASTS_HTTP_TIMEOUT = "API request timeout" + +[Links] + API = "https://www.mythic-beasts.com/support/api/dnsv2" + APIAuth = "https://www.mythic-beasts.com/support/api/auth" diff --git a/providers/dns/mythicbeasts/mythicbeasts_test.go b/providers/dns/mythicbeasts/mythicbeasts_test.go new file mode 100644 index 00000000..e49d1d57 --- /dev/null +++ b/providers/dns/mythicbeasts/mythicbeasts_test.go @@ -0,0 +1,153 @@ +package mythicbeasts + +import ( + "testing" + "time" + + "github.com/go-acme/lego/v3/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvUserName, + EnvPassword). + WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvUserName: "123", + EnvPassword: "456", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvUserName: "", + EnvPassword: "", + }, + expected: "mythicbeasts: some credentials information are missing: MYTHICBEASTS_USERNAME,MYTHICBEASTS_PASSWORD", + }, + { + desc: "missing api key", + envVars: map[string]string{ + EnvUserName: "", + EnvPassword: "api_password", + }, + expected: "mythicbeasts: some credentials information are missing: MYTHICBEASTS_USERNAME", + }, + { + desc: "missing secret key", + envVars: map[string]string{ + EnvUserName: "api_username", + EnvPassword: "", + }, + expected: "mythicbeasts: some credentials information are missing: MYTHICBEASTS_PASSWORD", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer envTest.RestoreEnv() + envTest.ClearEnv() + + envTest.Apply(test.envVars) + + p, err := NewDNSProvider() + + if len(test.expected) == 0 { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + username string + password string + expected string + }{ + { + desc: "success", + username: "api_username", + password: "api_password", + }, + { + desc: "missing credentials", + expected: "mythicbeasts: incomplete credentials, missing username and/or password", + }, + { + desc: "missing username", + username: "", + password: "api_password", + expected: "mythicbeasts: incomplete credentials, missing username and/or password", + }, + { + desc: "missing password", + username: "api_username", + password: "", + expected: "mythicbeasts: incomplete credentials, missing username and/or password", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config, err := NewDefaultConfig() + require.NoError(t, err) + config.UserName = test.username + config.Password = test.password + + p, err := NewDNSProviderConfig(config) + + if len(test.expected) == 0 { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestLivePresent(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.Present(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} + +func TestLiveCleanUp(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + time.Sleep(1 * time.Second) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +}