Add DNS provider for Mythic beasts DNSv2 (#1125)

This commit is contained in:
Daniel Silverstone 2020-04-28 08:04:22 +01:00 committed by GitHub
parent 4fe3bcfc6f
commit 4057c38364
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 680 additions and 7 deletions

View file

@ -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/) | | [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/) | | [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/) | | [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/) | | [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/) |
| [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/) | | [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/) |
| [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/) | | [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/) |
| [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/) | | [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/) |
| [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/) | | [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/) |
| [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/) | | [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/) |
| [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/) | | [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/) | | | |
<!-- END DNS PROVIDERS LIST --> <!-- END DNS PROVIDERS LIST -->

View file

@ -57,6 +57,7 @@ func allDNSCodes() string {
"linodev4", "linodev4",
"liquidweb", "liquidweb",
"mydnsjp", "mydnsjp",
"mythicbeasts",
"namecheap", "namecheap",
"namedotcom", "namedotcom",
"namesilo", "namesilo",
@ -1003,6 +1004,29 @@ func displayDNSHelp(name string) error {
ew.writeln() ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/mydnsjp`) 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": case "namecheap":
// generated from: providers/dns/namecheap/namecheap.toml // generated from: providers/dns/namecheap/namecheap.toml
ew.writeln(`Configuration for Namecheap.`) ew.writeln(`Configuration for Namecheap.`)

View file

@ -0,0 +1,69 @@
---
title: "MythicBeasts"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: mythicbeasts
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/mythicbeasts/mythicbeasts.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Since: v0.3.7
Configuration for [MythicBeasts](https://www.mythic-beasts.com/).
<!--more-->
- 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)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/mythicbeasts/mythicbeasts.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -48,6 +48,7 @@ import (
"github.com/go-acme/lego/v3/providers/dns/linodev4" "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/liquidweb"
"github.com/go-acme/lego/v3/providers/dns/mydnsjp" "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/namecheap"
"github.com/go-acme/lego/v3/providers/dns/namedotcom" "github.com/go-acme/lego/v3/providers/dns/namedotcom"
"github.com/go-acme/lego/v3/providers/dns/namesilo" "github.com/go-acme/lego/v3/providers/dns/namesilo"
@ -169,6 +170,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return dns01.NewDNSProviderManual() return dns01.NewDNSProviderManual()
case "mydnsjp": case "mydnsjp":
return mydnsjp.NewDNSProvider() return mydnsjp.NewDNSProvider()
case "mythicbeasts":
return mythicbeasts.NewDNSProvider()
case "namecheap": case "namecheap":
return namecheap.NewDNSProvider() return namecheap.NewDNSProvider()
case "namedotcom": case "namedotcom":

View file

@ -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
}

View file

@ -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
}

View file

@ -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"

View file

@ -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)
}