diff --git a/README.md b/README.md
index 1e16afeb..a46f8fb7 100644
--- a/README.md
+++ b/README.md
@@ -184,48 +184,53 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
RcodeZero |
reg.ru |
+ Regfish |
RFC2136 |
- RimuHosting |
+ RimuHosting |
Sakura Cloud |
Scaleway |
Selectel |
- Selectel v2 |
+ Selectel v2 |
SelfHost.(de|eu) |
Servercow |
Shellrent |
- Simply.com |
+ Simply.com |
Sonic |
Stackpath |
Tencent Cloud DNS |
- Timeweb Cloud |
+ Timeweb Cloud |
TransIP |
UKFast SafeDNS |
Ultradns |
- Variomedia |
+ Variomedia |
VegaDNS |
Vercel |
Versio.[nl|eu|uk] |
- VinylDNS |
+ VinylDNS |
VK Cloud |
Volcano Engine/火山引擎 |
Vscale |
- Vultr |
+ Vultr |
Webnames |
Websupport |
WEDOS |
- Yandex 360 |
+ Yandex 360 |
Yandex Cloud |
Yandex PDD |
Zone.ee |
+
Zonomi |
+ |
+ |
+ |
diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go
index 34dac261..39bce16d 100644
--- a/cmd/zz_gen_cmd_dnshelp.go
+++ b/cmd/zz_gen_cmd_dnshelp.go
@@ -116,6 +116,7 @@ func allDNSCodes() string {
"porkbun",
"rackspace",
"rcodezero",
+ "regfish",
"regru",
"rfc2136",
"rimuhosting",
@@ -2375,6 +2376,26 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/rcodezero`)
+ case "regfish":
+ // generated from: providers/dns/regfish/regfish.toml
+ ew.writeln(`Configuration for Regfish.`)
+ ew.writeln(`Code: 'regfish'`)
+ ew.writeln(`Since: 'v4.20.0'`)
+ ew.writeln()
+
+ ew.writeln(`Credentials:`)
+ ew.writeln(` - "REGFISH_API_KEY": API key`)
+ ew.writeln()
+
+ ew.writeln(`Additional Configuration:`)
+ ew.writeln(` - "REGFISH_HTTP_TIMEOUT": API request timeout`)
+ ew.writeln(` - "REGFISH_POLLING_INTERVAL": Time between DNS propagation check`)
+ ew.writeln(` - "REGFISH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
+ ew.writeln(` - "REGFISH_TTL": The TTL of the TXT record used for the DNS challenge`)
+
+ ew.writeln()
+ ew.writeln(`More information: https://go-acme.github.io/lego/dns/regfish`)
+
case "regru":
// generated from: providers/dns/regru/regru.toml
ew.writeln(`Configuration for reg.ru.`)
diff --git a/docs/content/dns/zz_gen_regfish.md b/docs/content/dns/zz_gen_regfish.md
new file mode 100644
index 00000000..7ab8c143
--- /dev/null
+++ b/docs/content/dns/zz_gen_regfish.md
@@ -0,0 +1,68 @@
+---
+title: "Regfish"
+date: 2019-03-03T16:39:46+01:00
+draft: false
+slug: regfish
+dnsprovider:
+ since: "v4.20.0"
+ code: "regfish"
+ url: "https://regfish.de/"
+---
+
+
+
+
+
+
+Configuration for [Regfish](https://regfish.de/).
+
+
+
+
+- Code: `regfish`
+- Since: v4.20.0
+
+
+Here is an example bash command using the Regfish provider:
+
+```bash
+REGFISH_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \
+lego --email myemail@example.com --dns regfish --domains my.example.org run
+```
+
+
+
+
+## Credentials
+
+| Environment Variable Name | Description |
+|-----------------------|-------------|
+| `REGFISH_API_KEY` | API key |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here]({{% ref "dns#configuration-and-credentials" %}}).
+
+
+## Additional Configuration
+
+| Environment Variable Name | Description |
+|--------------------------------|-------------|
+| `REGFISH_HTTP_TIMEOUT` | API request timeout |
+| `REGFISH_POLLING_INTERVAL` | Time between DNS propagation check |
+| `REGFISH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
+| `REGFISH_TTL` | The TTL of the TXT record used for the DNS challenge |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here]({{% ref "dns#configuration-and-credentials" %}}).
+
+
+
+
+## More information
+
+- [API documentation](https://regfish.readme.io/)
+- [Go client](https://github.com/regfish/regfish-dnsapi-go)
+
+
+
+
diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml
index a389c793..efbd36bc 100644
--- a/docs/data/zz_cli_help.toml
+++ b/docs/data/zz_cli_help.toml
@@ -141,7 +141,7 @@ To display the documentation for a specific DNS provider, run:
$ lego dnshelp -c code
Supported DNS providers:
- acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manual, metaname, mijnhost, mittwald, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
+ acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manual, metaname, mijnhost, mittwald, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
More information: https://go-acme.github.io/lego/dns
"""
diff --git a/go.mod b/go.mod
index a53df9e1..3224aa34 100644
--- a/go.mod
+++ b/go.mod
@@ -62,6 +62,7 @@ require (
github.com/ovh/go-ovh v1.6.0
github.com/pquerna/otp v1.4.0
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2
+ github.com/regfish/regfish-dnsapi-go v0.1.1
github.com/sacloud/api-client-go v0.2.10
github.com/sacloud/iaas-api-go v1.12.0
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30
diff --git a/go.sum b/go.sum
index 29fbb58c..9ed0d8e3 100644
--- a/go.sum
+++ b/go.sum
@@ -494,6 +494,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -731,6 +733,8 @@ github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 h1:dq90+d51/hQR
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/regfish/regfish-dnsapi-go v0.1.1 h1:TJFtbePHkd47q5GZwYl1h3DIYXmoxdLjW/SBsPtB5IE=
+github.com/regfish/regfish-dnsapi-go v0.1.1/go.mod h1:ubIgXSfqarSnl3XHSn8hIFwFF3h0yrq0ZiWD93Y2VjY=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
diff --git a/providers/dns/regfish/regfish.go b/providers/dns/regfish/regfish.go
new file mode 100644
index 00000000..306c59bd
--- /dev/null
+++ b/providers/dns/regfish/regfish.go
@@ -0,0 +1,143 @@
+// Package regfish implements a DNS provider for solving the DNS-01 challenge using Regfish.
+package regfish
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "sync"
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge/dns01"
+ "github.com/go-acme/lego/v4/platform/config/env"
+ regfishapi "github.com/regfish/regfish-dnsapi-go"
+)
+
+// Environment variables names.
+const (
+ envNamespace = "REGFISH_"
+
+ EnvAPIKey = envNamespace + "API_KEY"
+
+ 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 {
+ APIKey string
+
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ TTL int
+ HTTPClient *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider.
+func NewDefaultConfig() *Config {
+ return &Config{
+ TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
+ PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
+ PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
+ HTTPClient: &http.Client{
+ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
+ },
+ }
+}
+
+// DNSProvider implements the challenge.Provider interface.
+type DNSProvider struct {
+ config *Config
+ client *regfishapi.Client
+
+ recordIDs map[string]int
+ recordIDsMu sync.Mutex
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for Regfish.
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get(EnvAPIKey)
+ if err != nil {
+ return nil, fmt.Errorf("regfish: %w", err)
+ }
+
+ config := NewDefaultConfig()
+ config.APIKey = values[EnvAPIKey]
+
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for Regfish.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("regfish: the configuration of the DNS provider is nil")
+ }
+
+ if config.APIKey == "" {
+ return nil, errors.New("regfish: credentials missing")
+ }
+
+ client := regfishapi.NewClient(config.APIKey)
+
+ return &DNSProvider{
+ config: config,
+ client: client,
+ recordIDs: make(map[string]int),
+ }, nil
+}
+
+// Present creates a TXT record using the specified parameters.
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ info := dns01.GetChallengeInfo(domain, keyAuth)
+
+ record := regfishapi.Record{
+ Name: info.EffectiveFQDN,
+ Type: "TXT",
+ Data: info.Value,
+ TTL: d.config.TTL,
+ }
+
+ newRecord, err := d.client.CreateRecord(record)
+ if err != nil {
+ return fmt.Errorf("regfish: create record: %w", err)
+ }
+
+ d.recordIDsMu.Lock()
+ d.recordIDs[token] = newRecord.ID
+ d.recordIDsMu.Unlock()
+
+ return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters.
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ info := dns01.GetChallengeInfo(domain, keyAuth)
+
+ // get the record's unique ID from when we created it
+ d.recordIDsMu.Lock()
+ recordID, ok := d.recordIDs[token]
+ d.recordIDsMu.Unlock()
+ if !ok {
+ return fmt.Errorf("regfish: unknown record ID for '%s'", info.EffectiveFQDN)
+ }
+
+ err := d.client.DeleteRecord(recordID)
+ if err != nil {
+ return fmt.Errorf("regfish: delete record: %w", err)
+ }
+
+ // Delete record ID from map
+ d.recordIDsMu.Lock()
+ delete(d.recordIDs, token)
+ d.recordIDsMu.Unlock()
+
+ return nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
diff --git a/providers/dns/regfish/regfish.toml b/providers/dns/regfish/regfish.toml
new file mode 100644
index 00000000..822ba990
--- /dev/null
+++ b/providers/dns/regfish/regfish.toml
@@ -0,0 +1,23 @@
+Name = "Regfish"
+Description = ''''''
+URL = "https://regfish.de/"
+Code = "regfish"
+Since = "v4.20.0"
+
+Example = '''
+REGFISH_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \
+lego --email myemail@example.com --dns regfish --domains my.example.org run
+'''
+
+[Configuration]
+ [Configuration.Credentials]
+ REGFISH_API_KEY = "API key"
+ [Configuration.Additional]
+ REGFISH_POLLING_INTERVAL = "Time between DNS propagation check"
+ REGFISH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
+ REGFISH_TTL = "The TTL of the TXT record used for the DNS challenge"
+ REGFISH_HTTP_TIMEOUT = "API request timeout"
+
+[Links]
+ API = "https://regfish.readme.io/"
+ GoClient = "https://github.com/regfish/regfish-dnsapi-go"
diff --git a/providers/dns/regfish/regfish_test.go b/providers/dns/regfish/regfish_test.go
new file mode 100644
index 00000000..80928048
--- /dev/null
+++ b/providers/dns/regfish/regfish_test.go
@@ -0,0 +1,113 @@
+package regfish
+
+import (
+ "testing"
+
+ "github.com/go-acme/lego/v4/platform/tester"
+ "github.com/stretchr/testify/require"
+)
+
+const envDomain = envNamespace + "DOMAIN"
+
+var envTest = tester.NewEnvTest(EnvAPIKey).WithDomain(envDomain)
+
+func TestNewDNSProvider(t *testing.T) {
+ testCases := []struct {
+ desc string
+ envVars map[string]string
+ expected string
+ }{
+ {
+ desc: "success",
+ envVars: map[string]string{
+ EnvAPIKey: "secret",
+ },
+ },
+ {
+ desc: "missing credentials",
+ envVars: map[string]string{},
+ expected: "regfish: some credentials information are missing: REGFISH_API_KEY",
+ },
+ }
+
+ 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 test.expected == "" {
+ require.NoError(t, err)
+ require.NotNil(t, p)
+ require.NotNil(t, p.config)
+ require.NotNil(t, p.client)
+ } else {
+ require.EqualError(t, err, test.expected)
+ }
+ })
+ }
+}
+
+func TestNewDNSProviderConfig(t *testing.T) {
+ testCases := []struct {
+ desc string
+ apiKey string
+ expected string
+ }{
+ {
+ desc: "success",
+ apiKey: "secret",
+ },
+ {
+ desc: "missing credentials",
+ expected: "regfish: credentials missing",
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.desc, func(t *testing.T) {
+ config := NewDefaultConfig()
+ config.APIKey = test.apiKey
+
+ p, err := NewDNSProviderConfig(config)
+
+ if test.expected == "" {
+ require.NoError(t, err)
+ require.NotNil(t, p)
+ require.NotNil(t, p.config)
+ require.NotNil(t, p.client)
+ } 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)
+
+ err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
+ require.NoError(t, err)
+}
diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go
index af6b8982..63f16db9 100644
--- a/providers/dns/zz_gen_dns_providers.go
+++ b/providers/dns/zz_gen_dns_providers.go
@@ -110,6 +110,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/porkbun"
"github.com/go-acme/lego/v4/providers/dns/rackspace"
"github.com/go-acme/lego/v4/providers/dns/rcodezero"
+ "github.com/go-acme/lego/v4/providers/dns/regfish"
"github.com/go-acme/lego/v4/providers/dns/regru"
"github.com/go-acme/lego/v4/providers/dns/rfc2136"
"github.com/go-acme/lego/v4/providers/dns/rimuhosting"
@@ -359,6 +360,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return rackspace.NewDNSProvider()
case "rcodezero":
return rcodezero.NewDNSProvider()
+ case "regfish":
+ return regfish.NewDNSProvider()
case "regru":
return regru.NewDNSProvider()
case "rfc2136":